为什么在相同计划中,(相同)1000个唯一索引的估计成本不同?


27

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

查询2

以上内容已在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个行的登台表,以上两个仍然具有相同的计划形状(带有嵌套循环),并且该计划在没有派生表的情况下看起来更便宜,但是对于10,000行的登台表和相同的目标表,上述成本差异确实改变了计划形状(完整扫描和合并连接似乎比昂贵的搜索更具吸引力)表明,这种成本差异可能会带来影响,而不仅仅是使比较计划变得更加困难。

在此处输入图片说明

Answers:


20

有人知道为什么第一个计划的成本为0.172434,而第二个计划的成本为3.01702吗?

一般而言,在嵌套循环连接下方的内侧搜索是在假定随机I / O模式的情况下进行的。后续访问有一个基于替换的简单减少,这考虑了所需的页面已被先前的迭代带入内存的机会。此基本评估产生标准(较高)成本。

还有另一个成本核算输入,即Smart Seek Costing,关于它的详细信息很少。我的猜测(这就是本阶段的全部内容)是SSC试图更详细地评估内部寻找I / O成本,也许是通过考虑本地排序和/或要获取的值的范围。谁知道。

例如,第一个查找操作不仅引入请求的行,还引入该页面上的所有行(按索引顺序)。给定整体访问模式,即使禁用了预读和预取功能,也可以在2次物理读取中获取1000个搜索中的1000行。从这个角度来看,默认的I / O成本表示过高的估计,而SSC调整后的成本更接近实际。

可以合理地预期,在循环或多或少直接驱动索引查找并且连接外部引用是查找操作的基础的情况下,SSC将是最有效的。据我所知,SSC总是尝试进行适当的物理操作,但是当将查找与其他操作分开时,SSC通常不会进行向下调整。简单过滤器是对此的一个例外,也许是因为SQL Server经常可以将它们推入数据访问运算符。无论如何,优化器对选择都有很深的支持。

不幸的是,此处子查询外部投影的“计算标量”似乎会干扰SSC。计算标量通常会在连接上方重新放置,但这些标量必须保留在原处。即便如此,大多数普通的Compute Scalars对优化而言都是透明的,因此这还是有点令人惊讶。

无论如何,当通过PhyOp_Range简单选择索引进行物理操作时SelIdxToRng,SSC都是有效的。当采用更复杂的方法SelToIdxStrategy(在表上选择索引策略)时,结果PhyOp_Range将运行SSC,但不会减少。同样,似乎更简单,更直接的操作最适合SSC。

我希望我能确切地告诉您SSC的功能,并显示确切的计算,但是我不知道这些细节。如果要浏览自己可用的有限跟踪输出,可以使用未记录的跟踪标志2398。示例输出为:

智能搜寻成本计算(7.1):: 1.34078e + 154,0.001

该示例与备忘组7(备选项1)相关,其中显示了费用上限和系数0.001。要查看更简洁的因素,请确保在没有并行的情况下重建表,以便页面尽可能地密集。如果不这样做,则对于示例目标表,该因子更像是0.000821。当然,那里存在一些相当明显的关系。

也可以使用未记录的跟踪标记2399禁用SSC。在激活该标记的情况下,这两个成本都是较高的值。


8

不确定这是否是答案,但是要发表评论有点长。造成这种差异的原因纯粹是我的猜测,也许可以为他人思考提供帮助。

带有执行计划的简化查询。

SELECT S.KeyCol, 
       S.OtherCol,
       T.*
FROM staging AS S 
  LEFT OUTER JOIN Target AS T 
    ON T.KeyCol = S.KeyCol;

SELECT S.KeyCol, 
       S.OtherCol,
       T.*
FROM staging AS S 
  LEFT OUTER JOIN (
                  SELECT *
                  FROM Target
                  ) AS T 
    ON T.KeyCol = S.KeyCol;

在此处输入图片说明

这些等效查询之间可能真正导致相同的执行计划的主要区别是计算标量运算符。我不知道为什么它必须存在,但我想这是优化器可以优化派生表的最大范围。

我的猜测是,计算标量的存在是在浪费第二次查询的IO成本。

优化器内部:计划成本核算

第一行的CPU成本计算为0.0001581,后续行的CPU成本计算为0.000011。
...
0.003125的I / O成本恰好是1/320 –反映了该模型的假设,即磁盘子系统每秒可以执行320个随机I / O操作
...
成本核算足够聪明,可以识别出磁盘总数需要从磁盘导入的页数永远不能超过存储整个表所需的页数。

在我的情况下,该表占用5618页,要从1000000行中获得1000行,估计的页面数应为5.618,从而得到IO成本为0.015625。

两个查询接缝的CPU成本相同0.0001581 * 1000 executions = 0.1581

因此,根据上面链接的文章,我们可以计算出第一个查询的成本为0.173725。

假设我对计算标量如何使IO成本一团糟是正确的,则可以将其计算为3.2831。

计划中所显示的不完全是,但就在附近。


6

(作为对Paul的回答的评论,这样会更好,但是我没有足够的代表。)

我想提供DBCC过去几乎得出结论的跟踪标志列表(以及一些陈述),以防将来调查相似的差异会有所帮助。所有这些都不应该在生产中使用

首先,我看了最终备忘录,以了解正在使用什么物理运算符。根据图形执行计划,它们的外观肯定相同。因此,我使用了跟踪标志36048615,第一个将输出定向到客户端,第二个显示最终备注:

SELECT S.*, T.KeyCol
FROM Staging AS S
      LEFT OUTER JOIN Target AS T
       ON T.KeyCol = S.KeyCol
OPTION(QUERYTRACEON 3604, -- Output client info
       QUERYTRACEON 8615, -- Shows Final Memo structure
       RECOMPILE);

从追溯Root Group,我发现了这些几乎相同的PhyOp_Range运算符:

  1. PhyOp_Range 1 ASC 2.0 Cost(RowGoal 0,ReW 0,ReB 999,Dist 1000,Total 1000)= 0.175559(Distance = 2)
  2. PhyOp_Range 1 ASC 3.0 Cost(RowGoal 0,ReW 0,ReB 999,Dist 1000,Total 1000)= 3.01702(Distance = 2)

对我而言,唯一明显的区别是2.03.0,分别指代各自的“备忘录组2”和“备忘录组3”。查看备忘录,它们指的是同一件事-因此尚未发现差异。

其次,我调查了整个混乱的跟踪标志,这些跟踪标志对我来说毫无用处-但有一些有趣的内容。我从本杰明·内瓦雷斯Benjamin Nevarez)举起了大部分东西。我一直在寻找关于在一种情况下而不是另一种情况下应用的优化规则的线索。

 SELECT S.*, T.KeyCol
 FROM Staging AS S
      LEFT OUTER JOIN Target AS T
        ON T.KeyCol = S.KeyCol
 OPTION (QUERYTRACEON 3604, -- Output info to client
         QUERYTRACEON 2363, -- Show stats and cardinality info
         QUERYTRACEON 8675, -- Show optimization process info
         QUERYTRACEON 8606, -- Show logical query trees
         QUERYTRACEON 8607, -- Show physical query tree
         QUERYTRACEON 2372, -- Show memory utilization info for optimization stages 
         QUERYTRACEON 2373, -- Show memory utilization info for applying rules
         RECOMPILE );

第三,我研究了适用于我们PhyOp_Range的规则的哪些规则是如此相似。我使用了Paul在博客文章中提到的几个跟踪标记。

SELECT S.*, T.KeyCol
FROM Staging AS S
      LEFT OUTER JOIN (SELECT KeyCol
                      FROM Target) AS T
       ON T.KeyCol = S.KeyCol
OPTION (QUERYTRACEON 3604, -- Output info to client
        QUERYTRACEON 8619, -- Show applied optimization rules
        QUERYTRACEON 8620, -- Show rule-to-memo info
        QUERYTRACEON 8621, -- Show resulting tree
        QUERYTRACEON 2398, -- Show "smart seek costing"
        RECOMPILE );

从输出中,我们看到直接JOIN应用此规则来获取PhyOp_Range运算符:Rule Result: group=7 2 <SelIdxToRng>PhyOp_Range 1 ASC 2 (Distance = 2)。子选择应用这个规则来代替:Rule Result: group=9 2 <SelToIdxStrategy>PhyOp_Range 1 ASC 3 (Distance = 2)。您也可以在这里看到与每个规则相关的“智能搜寻成本”信息。对于指示- JOIN这是输出(对我来说)Smart seek costing (7.2) :: 1.34078e+154 , 0.001。对于子选择,这是输出:Smart seek costing (9.2) :: 1.34078e+154 , 1

最后,我不能得出太多结论-但是Paul的答案弥补了大部分差距。我想了解更多有关智能搜寻成本计算的信息。


4

这也不是一个真正的答案-正如Mikael指出的那样,很难在评论中讨论这个问题...

有趣的是,如果将子查询(select KeyCol FROM Target)转换为嵌入式TVF,您会看到计划及其成本与简单的原始查询相同:

CREATE FUNCTION dbo.cs_test()
RETURNS TABLE
WITH SCHEMABINDING
AS 
RETURN (
    SELECT KeyCol FROM dbo.Target
    );

/* "normal" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN Target AS T ON T.KeyCol = S.KeyCol;

/* "subquery" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN (SELECT KeyCol FROM Target) AS T ON T.KeyCol = S.KeyCol;

/* "inline-TVF" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN dbo.cs_test() t ON s.KeyCol = t.Keycol

查询计划(pastetheplan链接):

在此处输入图片说明

推论使我相信成本核算引擎对这种子查询可能具有潜在影响感到困惑。

例如,以下内容:

SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN (
        SELECT KeyCol = CHECKSUM(NEWID()) 
        FROM Target
        ) AS T ON T.KeyCol = S.KeyCol;

会如何,你的成本是什么?查询优化器选择与上面的“ subquery”变体非常相似的计划,其中包含计算标量(pastetheplan.com链接):

在此处输入图片说明

计算标量的成本与上面显示的“子查询”变体有很大不同,但是由于查询优化器无法先验地知道返回的行数,因此这只是一个猜测。由于行估计是未知的,因此该计划对左外部联接使用哈希匹配,因此将其设置为“目标”表中的行数。

在此处输入图片说明

除了我同意Mikael在他的回答中所做的工作之外,我没有一个很好的结论,并希望其他人可以提出更好的答案。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.