为什么第一个执行计划不使用 RowCount Spool?

sep*_*pic 6 sql-server optimization execution-plan

这是我在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
Run Code Online (Sandbox Code Playgroud)

结果,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.
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

Mar*_*ith 6

这只是一个基于成本的决定。

表中大约有 2,000 行,包含v0到 的所有值4,没有 的其他值v

半连接可以在返回第一个行后停止请求行,因此 SQL Server 假定它在找到第一个匹配的行之前只需要读取 5 行v=4

添加having count(*) > 0时现在需要读取整个表来获取COUNT(*)并且在找到第一个匹配行后无法停止。和v=40这并不在数据都存在,但SQL服务器又需要读取整个表,以确保这一点。

为每个外部行读取整个表与仅从中读取几行的成本足以使具有行计数假脱机优化的计划具有吸引力。

您可以使用v=4和尝试以下操作,并v=40在文本差异工具中比较结果。

SELECT v
FROM   dbo.t
WHERE  EXISTS (SELECT 1 FROM dbo.t WHERE v = 40) 
OPTION (RECOMPILE, QUERYTRACEON 3604, QUERYTRACEON 8615);
Run Code Online (Sandbox Code Playgroud)

最终备忘录的结果在探索的选项方面非常具有可比性,但出于成本考虑选择了不同的最终结果。

选择了带线轴的计划,没有线轴的计划PhyOp_LoopsJoinx_jtLeftSemi 6.2 13.1PhyOp_LoopsJoinx_jtLeftSemi 6.2 8.2

6.2 是两个计划中相同的聚集索引扫描。

8.2 是使用过滤器执行 10,000 次的扫描。

13.1 是通过使用过滤器执行一次扫描而构建的线轴。

备忘录的这些部分复制如下。当v=4线轴1.0043的成本是但没有线轴的成本较低时(在0.867567

v=40

8.2 PhyOp_Filter 7.1 4.0  Cost(RowGoal 0,ReW 9999,ReB 0,Dist 0,Total 0)= 158.804 (Distance = 1)
13.1 PhyOp_Spool 8.4  Cost(RowGoal 0,ReW 9999,ReB 0,Dist 0,Total 0)= 1.03564 (Distance = 2)
Run Code Online (Sandbox Code Playgroud)

v=4

8.2 PhyOp_Filter 7.1 4.0  Cost(RowGoal 1,ReW 9999,ReB 0,Dist 0,Total 0)= 0.867567 (Distance = 1)
13.1 PhyOp_Spool 8.4  Cost(RowGoal 1,ReW 9999,ReB 0,Dist 0,Total 0)= 1.0043 (Distance = 2)
Run Code Online (Sandbox Code Playgroud)

该提示OPTION (NO_PERFORMANCE_SPOOL)v=40计划中进行比较。

我原以为这USE HINT ('DISABLE_OPTIMIZER_ROWGOAL')会对计划起到相反的作用,v=4但尽管8.2现在在备忘录中对小组进行了成本计算,但158.804它仍然被选中,并且整个计划的成本仅为0.939204只需要该输入中的一行。此外,有了这个提示,就没有PhyOp_Spool最终备忘录中无论如何可用的选项。

Paul White 在评论中指出,性能线轴可以通过使用 OPTION (QUERYTRACEON 8691).

如果运行以下批处理,则第二个查询应显示为批处理成本的 50% 以上(即,根据 SQL Server 的成本模型,成本更高)

declare @v int;
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) OPTION (QUERYTRACEON 8691);
Run Code Online (Sandbox Code Playgroud)