如果where
子句包含参数化值而不是字符串文字,我们发现SQL Server正在使用索引扫描而不是索引搜索。
以下是一个例子:
SQL Server在以下情况下执行索引扫描(where子句中的参数)
declare @val1 nvarchar(40), @val2 nvarchar(40);
set @val1 = 'val1';
set @val2 = 'val2';
select
min(id)
from
scor_inv_binaries
where
col1 in (@val1, @val2)
group by
col1
另一方面,以下查询执行索引搜索:
select
min(id)
from
scor_inv_binaries
where
col1 in ('val1', 'val2')
group by
col1
是否有人观察到类似的行为,以及他们如何修复此问题以确保查询执行索引搜索而不是索引扫描?
我们无法使用forceseek表提示,因为SQL Sserver 2005支持forceseek。
我也更新了统计数据。 非常感谢您的帮助。
答案 0 :(得分:17)
很好地回答你的问题为什么SQL Server这样做,答案是查询不是按逻辑顺序编译的,每个语句都是根据它自己的优点编译的, 因此,当生成select语句的查询计划时,优化器不知道@ val1和@ Val2将分别变为'val1'和'val2'。
当SQL Server不知道该值时,它必须最好地猜测该变量在表中出现的次数,这有时会导致次优计划。我的主要观点是,具有不同值的相同查询可以生成不同的计划。想象一下这个简单的例子:
IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL
DROP TABLE #T;
CREATE TABLE #T (ID INT IDENTITY PRIMARY KEY, Val INT NOT NULL, Filler CHAR(1000) NULL);
INSERT #T (Val)
SELECT TOP 991 1
FROM sys.all_objects a
UNION ALL
SELECT TOP 9 ROW_NUMBER() OVER(ORDER BY a.object_id) + 1
FROM sys.all_objects a;
CREATE NONCLUSTERED INDEX IX_T__Val ON #T (Val);
我在这里所做的就是创建一个简单的表,并为列val
添加值为1-10的1000行,但是1个出现991次,而另外9个只出现一次。这个问题的前提是:
SELECT COUNT(Filler)
FROM #T
WHERE Val = 1;
仅扫描整个表会比使用索引进行搜索更有效,然后执行991书签查找以获取Filler
的值,但是只有1行以下查询:
SELECT COUNT(Filler)
FROM #T
WHERE Val = 2;
执行索引查找会更有效率,单个书签查找可以获得Filler
的值(运行这两个查询会批准这个)
我很确定搜索和书签查找的切断实际上根据情况而有所不同,但它相当低。使用示例表,通过一些试验和错误,我发现在优化器通过索引查找和书签查找进行全表扫描之前,我需要Val
列有38行值2。
IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL
DROP TABLE #T;
DECLARE @I INT = 38;
CREATE TABLE #T (ID INT IDENTITY PRIMARY KEY, Val INT NOT NULL, Filler CHAR(1000) NULL);
INSERT #T (Val)
SELECT TOP (991 - @i) 1
FROM sys.all_objects a
UNION ALL
SELECT TOP (@i) 2
FROM sys.all_objects a
UNION ALL
SELECT TOP 8 ROW_NUMBER() OVER(ORDER BY a.object_id) + 2
FROM sys.all_objects a;
CREATE NONCLUSTERED INDEX IX_T__Val ON #T (Val);
SELECT COUNT(Filler), COUNT(*)
FROM #T
WHERE Val = 2;
因此,对于此示例,限制是匹配行的3.7%。
由于查询在使用变量时不知道将匹配多少行,因此最简单的方法是找出总行数,并将其除以不同值的总数。列,所以在这个例子中WHERE val = @Val
的估计行数是1000/10 = 100,实际算法比这更复杂,但是例如这样做。因此,当我们查看执行计划时:
DECLARE @i INT = 2;
SELECT COUNT(Filler)
FROM #T
WHERE Val = @i;
我们可以在这里(使用原始数据)看到估计的行数是100,但实际行是1.从前面的步骤我们知道,超过38行,优化器将选择聚簇索引扫描在索引搜索中,所以由于对行数的最佳猜测高于此值,因此未知变量的计划是聚簇索引扫描。
只是为了进一步证明这个理论,如果我们创建的表有1000行数字1-27均匀分布(所以估计的行数大约是1000/27 = 37.037)
IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL
DROP TABLE #T;
CREATE TABLE #T (ID INT IDENTITY PRIMARY KEY, Val INT NOT NULL, Filler CHAR(1000) NULL);
INSERT #T (Val)
SELECT TOP 27 ROW_NUMBER() OVER(ORDER BY a.object_id)
FROM sys.all_objects a;
INSERT #T (val)
SELECT TOP 973 t1.Val
FROM #T AS t1
CROSS JOIN #T AS t2
CROSS JOIN #T AS t3
ORDER BY t2.Val, t3.Val;
CREATE NONCLUSTERED INDEX IX_T__Val ON #T (Val);
然后再次运行查询,我们得到一个索引搜索计划:
DECLARE @i INT = 2;
SELECT COUNT(Filler)
FROM #T
WHERE Val = @i;
因此,希望能够全面涵盖您实现该计划的原因。现在我想下一个问题是你如何强制一个不同的计划,答案是,使用查询提示OPTION (RECOMPILE)
来强制查询在参数值已知的执行时编译。恢复原始数据,其中Val = 2
的最佳计划是查找,但使用变量产生带有索引扫描的计划,我们可以运行:
DECLARE @i INT = 2;
SELECT COUNT(Filler)
FROM #T
WHERE Val = @i;
GO
DECLARE @i INT = 2;
SELECT COUNT(Filler)
FROM #T
WHERE Val = @i
OPTION (RECOMPILE);
我们可以看到后者使用索引查找和键查找,因为它在执行时检查了变量的值,并选择了该特定值的最合适的计划。 OPTION (RECOMPILE)
的问题在于这意味着您无法利用缓存的查询计划,因此每次编译查询都需要额外的费用。
答案 1 :(得分:0)
尝试
declare @val1 nvarchar(40), @val2 nvarchar(40);
set @val1 = 'val1';
set @val2 = 'val2';
select
min(id)
from
scor_inv_binaries
where
col1 in (@val1, @val2)
group by
col1
OPTION (RECOMPILE)
答案 2 :(得分:0)
什么数据类型是col1?
你的变量是nvarchar而你的文字是varchar / char;如果col1是varchar / char,它可能正在进行索引扫描,以隐式地将col1中的每个值转换为nvarchar进行比较。
答案 3 :(得分:0)
我猜第一个查询是使用谓词,第二个查询是使用搜索谓词。
Seek Predicate是描述Seek的b-tree部分的操作。谓词是使用非键列描述附加过滤器的操作。根据描述,很明显Seek Predicate在搜索索引时优于Predicate,而在Predicate中,搜索在非键列上 - 这意味着搜索在页面文件本身的数据上。
欲了解更多详情,请访问: - https://social.msdn.microsoft.com/Forums/sqlserver/en-US/36a176c8-005e-4a7d-afc2-68071f33987a/predicate-and-seek-predicate
答案 4 :(得分:0)
我遇到了这个确切的问题,但似乎没有任何查询选项解决方案有任何效果。
结果我声明了一个 nvarchar(8) 作为参数,并且该表有一列 varchar(8)。
更改参数类型后,查询执行索引查找并立即运行。一定是优化器被转换搞砸了。
在这种情况下,这可能不是答案,但值得检查。