这是我在SQL Server 2008 R2
, 2012
,上测试过的复制品2016
。
第二个和第三个查询使用RowCount Spool
,为什么第一个不使用?
create table dbo.t (id int identity primary key, v int);
--create statistics ST_t__v on dbo.t(v) with norecompute;
insert into dbo.t (v)
select top (10000) rand(checksum(newid())) * 5
from master.dbo.spt_values a cross join
master.dbo.spt_values b;
go
declare @v int;
set statistics xml, io on;
select @v = v from dbo.t where exists(select 1 from dbo.t where v = 4);
select @v = v from dbo.t where exists(select 1 from dbo.t where v = 4 having count(*) > 0);
select @v = v from dbo.t where exists(select 1 from dbo.t where v = 40);
set statistics xml, io off;
go
drop table dbo.t;
go
结果,IO 统计信息为:
Table 't'. Scan count 2, ***logical reads 20024***, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 't'. Scan count 2, logical reads 48, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 't'. Scan count 2, logical reads 27, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
这只是一个基于成本的决定。
表中大约有 2,000 行包含
v
从0
到的所有值4
,没有其他值v
。半连接可以在返回第一个行后停止请求行,因此 SQL Server 假定它只需要读取 5 行就可以找到第一个匹配的行
v=4
。当您添加
having count(*) > 0
时,它现在需要读取整个表才能获得,COUNT(*)
并且在找到第一个匹配行后不能停止。v=40
数据中根本不存在这个,但是 SQL 服务器再次需要读取整个表以确保这一点。为每个外部行读取整个表的成本与仅从中读取几行的成本足以使具有行计数假脱机优化的计划具有吸引力。
v=4
您可以尝试使用and进行以下操作,并v=40
在文本差异工具中比较结果。最终备忘录的结果在探索的选项方面非常具有可比性,但基于成本原因选择了不同的最终结果。
有线轴的计划选择了
PhyOp_LoopsJoinx_jtLeftSemi 6.2 13.1
没有它PhyOp_LoopsJoinx_jtLeftSemi 6.2 8.2
。6.2
是两个计划中相同的聚簇索引扫描。8.2
是使用过滤器执行 10,000 次的扫描。13.1
是通过使用过滤器执行一次扫描而构建的假脱机。备忘录的那些部分复制如下。当
v=4
线轴的成本是1.0043
但没有线轴的成本较低时(在0.867567
)v=40
v=4
该提示
OPTION (NO_PERFORMANCE_SPOOL)
从计划中删除了行计数假脱机以v=40
进行比较。我原以为这
USE HINT ('DISABLE_OPTIMIZER_ROWGOAL')
会对v=4
计划做相反的事情,但尽管8.2
现在在备忘录中对组进行了成本计算,158.804
但它仍然会被选中,并且整个计划的成本仅为0.939204
anti semi join 成本计算本身仍然会降低成本以反映它将只需要来自该输入的一行。PhyOp_Spool
此外,有了这个提示,最终备忘录中就没有可用的选项了。Paul White 在评论中指出,性能假脱机可以通过使用来实现
OPTION (QUERYTRACEON 8691)
。如果运行以下批次,则第二个查询应显示为超过批次成本的 50%(即根据 SQL Server 的成本模型更昂贵)