在下面的查询中,估计两个执行计划对唯一索引执行 1,000 次查找。
搜索是由对同一源表的有序扫描驱动的,因此看起来应该最终以相同的顺序搜索相同的值。
两个嵌套循环都有<NestedLoops Optimized="false" WithOrderedPrefetch="true">
任何人都知道为什么这个任务在第一个计划中的成本为 0.172434 而在第二个计划中为 3.01702?
(问题的原因是,由于计划成本明显低得多,向我建议第一个查询作为优化。在我看来,它实际上似乎做了更多的工作,但我只是试图解释差异.. .)
CREATE TABLE dbo.Target(KeyCol int PRIMARY KEY, OtherCol char(32) NOT NULL);
CREATE TABLE dbo.Staging(KeyCol int PRIMARY KEY, OtherCol char(32) NOT NULL);
INSERT INTO dbo.Target
SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY @@SPID), LEFT(NEWID(),32)
FROM master..spt_values v1,
master..spt_values v2;
INSERT INTO dbo.Staging
SELECT TOP (1000) ROW_NUMBER() OVER (ORDER BY @@SPID), LEFT(NEWID(),32)
FROM master..spt_values v1;
WITH T
AS (SELECT *
FROM Target AS T
WHERE T.KeyCol IN (SELECT S.KeyCol
FROM Staging AS S))
MERGE T
USING Staging S
ON ( T.KeyCol = S.KeyCol )
WHEN NOT MATCHED THEN
INSERT ( KeyCol, OtherCol )
VALUES(S.KeyCol, S.OtherCol )
WHEN MATCHED AND T.OtherCol > S.OtherCol THEN
UPDATE SET T.OtherCol = S.OtherCol;
MERGE Target T
USING Staging S
ON ( T.KeyCol = S.KeyCol )
WHEN NOT MATCHED THEN
INSERT ( KeyCol, OtherCol )
VALUES( S.KeyCol, S.OtherCol )
WHEN MATCHED AND T.OtherCol > S.OtherCol THEN
UPDATE SET T.OtherCol = S.OtherCol;
以上是在 SQL Server 2014 (SP2) (KB3171021) - 12.0.5000.0 (X64) 上测试的
@Joe Obbish在评论中指出,更简单的复制将是
SELECT *
FROM staging AS S
LEFT OUTER JOIN Target AS T
ON T.KeyCol = S.KeyCol;
对比
SELECT *
FROM staging AS S
LEFT OUTER JOIN (SELECT * FROM Target) AS T
ON T.KeyCol = S.KeyCol;
对于 1,000 行 staging 表,上述两者仍然具有相同的计划形状,带有嵌套循环,并且没有派生表的计划看起来更便宜,但是对于 10,000 行 staging 表和与上面相同的目标表,成本差异确实改变了计划形状(完全扫描和合并连接似乎比成本高昂的搜索更具吸引力)显示这种成本差异可能会产生影响,而不仅仅是使比较计划变得更加困难。
任何人都知道为什么这个任务在第一个计划中的成本为 0.172434 而在第二个计划中为 3.01702?
一般来说,嵌套循环连接下方的内侧查找是在假设随机 I/O 模式的情况下进行计算的。后续访问有一个简单的基于替换的减少,考虑到所需页面已经被先前的迭代带入内存的可能性。这种基本评估产生标准(更高)成本。
还有另一个成本核算输入,Smart Seek Costing,对此知之甚少。我的猜测(这就是现阶段的全部内容)是 SSC 尝试更详细地评估内部搜索 I/O 成本,可能是通过考虑本地排序和/或要获取的值的范围。谁知道。
例如,第一次查找操作不仅会引入请求的行,还会引入该页面上的所有行(按索引顺序)。考虑到整体访问模式,在 1000 次查找中获取 1000 行仅需要 2 次物理读取,即使禁用预读和预取也是如此。从这个角度来看,默认的 I/O 成本被高估了,而 SSC 调整后的成本更接近现实。
在循环或多或少直接驱动索引搜索的情况下,SSC 会最有效,并且连接外部引用是搜索操作的基础,这似乎是合理的。据我所知,SSC 总是尝试进行合适的物理操作,但是当其他操作将搜索与连接分开时,通常不会产生向下调整。简单过滤器是一个例外,可能是因为 SQL Server 通常可以将它们推送到数据访问运算符中。无论如何,优化器对选择有相当深入的支持。
不幸的是,子查询外部投影的计算标量似乎在这里干扰了 SSC。计算标量通常重新定位在连接之上,但这些必须留在原处。即便如此,大多数普通的计算标量对优化都是相当透明的,所以这有点令人惊讶。
无论如何,当物理操作PhyOp_Range
是通过对索引的简单选择产生的时SelIdxToRng
,SSC 是有效的。当采用更复杂的SelToIdxStrategy
(在表上选择索引策略)时,结果PhyOp_Range
会运行 SSC,但不会导致减少。同样,似乎更简单、更直接的操作最适合 SSC。
我希望我能准确地告诉你 SSC 做了什么,并显示了确切的计算结果,但我不知道这些细节。如果您想探索自己可用的有限跟踪输出,可以使用未记录的跟踪标志 2398。示例输出是:
Smart seek costing (7.1) :: 1.34078e+154 , 0.001
该示例与备忘录组 7、备选方案 1 相关,显示成本上限和系数 0.001。要查看更清晰的因素,请确保在没有并行性的情况下重建表,以便页面尽可能密集。如果不这样做,对于您的示例目标表,该因子更像是 0.000821。当然,那里有一些相当明显的关系。
SSC 也可以使用未记录的跟踪标志 2399 禁用。在该标志处于活动状态时,这两个成本都是较高的值。