IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID('t') AND type in (N'U')) DROP TABLE t GO CREATE TABLE t ( c1 int IDENTITY(1,1) NOT NULL,c2 int NULL ) GO insert into t select top 1000000 a from (select t1.number*2048 + t2.number a,newid() b from [master]..spt_values t1 cross join [master]..spt_values t2 where t1.[type] = 'P' and t2.[type] = 'P') a order by b GO update t set c2 = null where c2 < 2048 * 2048 / 10 GO CREATE CLUSTERED INDEX pk ON [t] (c1) GO CREATE NONCLUSTERED INDEX i ON t (c2) GO
select * from t where c2 < 1048576 or c2 is null ;
令我惊讶的是,为此查询生成的执行计划是this.(对不起外部链接,它太大了,不适合这里).
有人可以向我解释所有这些“Constant Scans”和“Compute Scalars”的内容吗?发生了什么?
|--Nested Loops(Inner Join,OUTER REFERENCES:([Expr1010],[Expr1011],[Expr1012])) |--Merge Interval | |--Sort(TOP 2,ORDER BY:([Expr1013] DESC,[Expr1014] ASC,[Expr1010] ASC,[Expr1015] DESC)) | |--Compute Scalar(DEFINE:([Expr1013]=((4)&[Expr1012]) = (4) AND NULL = [Expr1010],[Expr1014]=(4)&[Expr1012],[Expr1015]=(16)&[Expr1012])) | |--Concatenation | |--Compute Scalar(DEFINE:([Expr1005]=NULL,[Expr1006]=NULL,[Expr1004]=(60))) | | |--Constant Scan | |--Compute Scalar(DEFINE:([Expr1008]=NULL,[Expr1009]=(1048576),[Expr1007]=(10))) | |--Constant Scan |--Index Seek(OBJECT:([t].[i]),SEEK:([t].[c2] > [Expr1010] AND [t].[c2] < [Expr1011]) ORDERED FORWARD)
解决方法
Expr1005 Expr1006 Expr1004 ----------- ----------- ----------- NULL NULL 60
Expr1008 Expr1009 Expr1007 ----------- ----------- ----------- NULL 1048576 10
Expr1010 Expr1011 Expr1012 ----------- ----------- ----------- NULL NULL 60 NULL 1048576 10
Expr1012列是一组标志used internally to define certain seek properties for the Storage Engine.
输出2行的下一个计算标量
Expr1010 Expr1011 Expr1012 Expr1013 Expr1014 Expr1015 ----------- ----------- ----------- ----------- ----------- ----------- NULL NULL 60 True 4 16 NULL 1048576 10 False 0 0
最后三列定义如下,在呈现给合并间隔运算符之前仅用于排序目的
[Expr1013] = Scalar Operator(((4)&[Expr1012]) = (4) AND NULL = [Expr1010]),[Expr1014] = Scalar Operator((4)&[Expr1012]),[Expr1015] = Scalar Operator((16)&[Expr1012])
Expr1014和Expr1015只是测试标志中的某些位是否打开.
如果4的位都打开且Expr1010为NULL,则Expr1013似乎返回布尔列true.
通过在查询中尝试其他比较运算符,我得到了这些结果
+----------+----------+----------+-------------+----+----+---+---+---+---+ | Operator | Expr1010 | Expr1011 | Flags (Dec) | Flags (Bin) | | | | | | 32 | 16 | 8 | 4 | 2 | 1 | +----------+----------+----------+-------------+----+----+---+---+---+---+ | > | 1048576 | NULL | 6 | 0 | 0 | 0 | 1 | 1 | 0 | | >= | 1048576 | NULL | 22 | 0 | 1 | 0 | 1 | 1 | 0 | | <= | NULL | 1048576 | 42 | 1 | 0 | 1 | 0 | 1 | 0 | | < | NULL | 1048576 | 10 | 0 | 0 | 1 | 0 | 1 | 0 | | = | 1048576 | 1048576 | 62 | 1 | 1 | 1 | 1 | 1 | 0 | | IS NULL | NULL | NULL | 60 | 1 | 1 | 1 | 1 | 0 | 0 | +----------+----------+----------+-------------+----+----+---+---+---+---+
从中我推断Bit 4的意思是“具有范围的开始”(而不是无界),而第16位意味着范围的起点是包容性的.
这个6列结果集是从SORT运算符中排出的
Expr1013 DESC,Expr1014 ASC,Expr1010 ASC,Expr1015 DESC.假设True表示为1而False表示为0,则先前表示的结果集已按此顺序表示.
根据我先前的假设,这种净效应是按以下顺序将范围呈现给合并间隔
ORDER BY HasStartOfRangeAndItIsNullFirst,HasUnboundedStartOfRangeFirst,StartOfRange,StartOfRangeIsInclusiveFirst
合并间隔运算符输出2行
Expr1010 Expr1011 Expr1012 ----------- ----------- ----------- NULL NULL 60 NULL 1048576 10
对于发射的每一行,执行范围搜索
Seek Keys[1]: Start:[dbo].[t].c2 > Scalar Operator([Expr1010]),End: [dbo].[t].c2 < Scalar Operator([Expr1011])
所以看起来似乎有两次搜寻.一个显然> NULL AND< NULL和一个> NULL AND<然而,传入的标志似乎将其修改为IS NULL和< 1048576.分别为1048576.希望@sqlkiwi可以澄清这一点并纠正任何不准确之处!
如果您稍微更改查询
select * from t where c2 > 1048576 or c2 = 0 ;
该计划显示了Seek Keys
Start: c2 >= 0,End: c2 <= 0,Start: c2 > 1048576
sqlKiwi在对earlier linked blog post的评论中给出了为什么这个更简单的计划不能用于OP案例的解释.
具有多个谓词的索引查找不能混合不同类型的比较谓词(即,在OP中的情况下为Is和Eq).这只是产品的当前限制(并且可能是为什么使用> =和< =而不是仅仅为查询获得的直接相等性来实现上一个查询c2 = 0中的相等性测试的原因c2 = 0或c2 = 1048576.