一尘不染

为什么在这些计划中,(相同的)1000 次查找唯一索引的估计成本不同?

sql-server

在下面的查询中,估计两个执行计划对唯一索引执行 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;

查询1 “粘贴计划”链接

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;

查询 2 “粘贴计划”链接

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; 

查询 1

img

查询 2

img

以上是在 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 表和与上面相同的目标表,成本差异确实改变了计划形状(完全扫描和合并连接似乎比成本高昂的搜索更具吸引力)显示这种成本差异可能会产生影响,而不仅仅是使比较计划变得更加困难。

在此处输入图像描述


阅读 94

收藏
2022-11-09

共1个答案

一尘不染

任何人都知道为什么这个任务在第一个计划中的成本为 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 禁用。在该标志处于活动状态时,这两个成本都是较高的值。

2022-11-09