SQL Server 如何估计嵌套循环索引查找的基数

SEa*_*986 3 sql-server execution-plan cardinality-estimates sql-server-2019

我试图了解 SQL Server 如何估计下面的 Stack Overflow 数据库查询的基数

首先,我创建索引

CREATE INDEX IX_PostId ON dbo.Comments
(
    PostId
)
INCLUDE
(
    [Text]
)
Run Code Online (Sandbox Code Playgroud)

这是查询:

SELECT  u.DisplayName,
        c.PostId,
        c.Text
FROM    Users u
        JOIN Comments c
            ON u.Reputation = c.PostId
WHERE   u.AccountId = 22547
Run Code Online (Sandbox Code Playgroud)

执行计划在这里

首先,SQL Server 扫描用户表上的聚集索引以返回与 AccountId 谓词匹配的用户。我可以看到它使用了这个统计数据:_WA_Sys_0000000E_09DE7BCC

在此输入图像描述

我可以看到该用户没有范围高键,因此 SQL Server 使用 avg_range 行并估计 1

在此输入图像描述

评论索引搜索的搜索谓词是

在此输入图像描述

soScalar Operator([StackOverflow2010].[dbo].[Users].[Reputation] as [u].[Reputation]表示users表中accountId为的用户的信誉值22547

我可以看到总共加载了三个统计数据:

_WA_Sys_0000000E_09DE7BCC- Users.AccountId(用于估计聚集索引查找谓词)

IX_PostId- Comments.PostId(用于估计索引查找谓词)

_WA_Sys_0000000A_09DE7BCC- 用户.声誉 (?)

SQL Server 如何得出索引查找的估计值?它无法在编译时知道 accountId 的信誉,22547因为帐户 ID 统计数据没有显示这一点,因此它无法在直方图上查找 IX_PostId。我可以看到信誉统计数据也已加载,那么它是否以某种方式使用两者?

此查询针对 CE 150 运行

Pau*_*ite 6

\n

SQL Server 如何得出索引查找的估计值?它在编译时无法知道 accountId 22547 的信誉,因为帐户 ID 统计数据没有显示这一点,因此它无法在直方图上查找 IX_PostId。

\n
\n

在这种特殊情况下,SQL Server 不会为 Comments 表上的索引查找导出基数估计,因为它不需要这样做。让我稍微解释一下该声明:

\n

编译过程始终涉及第一轮基数估计,其中估计是根据简化后查询逻辑表示的早期形状得出的。您的示例中的逻辑树是:

\n
LogOp_Join\n    LogOp_Select\n    LogOp_Get TBL: dbo.Users(alias TBL: U)\n        ScaOp_Comp x_cmpEq\n            ScaOp_Identifier [U].AccountId\n            ScaOp_Const Value=22547\n    LogOp_Get TBL: dbo.Comments(alias TBL: C)\n        ScaOp_Comp x_cmpEq\n        ScaOp_Identifier QCOL: [C].PostId\n        ScaOp_Identifier QCOL: [U].Reputation\n
Run Code Online (Sandbox Code Playgroud)\n

需要两个初步估计:

\n
    \n
  1. 过滤用户.AccountId = 22547 ( LogOp_Select)
  2. \n
  3. LogOp_JoinComments.PostId = Users.Reputation ( )上的内部联接
  4. \n
\n

暂时抛开这些计算的细节,事实是 SQL Server 确实以某种方式导出了两者的估计选择性(和基数)。假设过滤器后的基数估计为C 1,连接后的基数估计为C 2。

\n

在后续基于成本的优化过程中,SQL Server 会考虑不同的方式来实现连接。例如,它可能会得出Merge Join、Hash Join、Nested Loops Join或Apply(相关循环连接)的估计成本。

\n

当考虑应用(使用类似 的规则)时,优化器已经对连接的顶部输入C 1JNtoIdxLookup进行了估计。它还已经知道连接结果C 2的基数。为了便于讨论,假设C 1为 10,C 2为 250。

\n

无需对较低的Apply进行新的估算。我们知道它将执行 10 次(每个上面的输入行一次)并且连接总共将产生 250 行。因此,下部输入的每次迭代都需要生成 25 行才能使数字相加,即 10 * 25 = 250。

\n

您的问题的简单答案是,在这种情况下,优化器不会为索引seek\xe2\x80\x94生成基数估计,它是直接从现有的连接和过滤器估计中派生出来的,通过考虑实现逻辑的规则作为Apply with Index Lookup加入。

\n

其他详情

\n

无可否认,选择性计算可能极其复杂。我在上面给了你一个简单的答案,因为这似乎解决了你的问题。其他人可能想要更多细节。

\n

我无法在这里描述整个估计框架,因为我不知道所有细节,即使我知道,也需要几本书才能涵盖。也就是说,有一些值得一提的事情以及一些其他资源可供感兴趣的读者链接。

\n

Stack Overflow 示例数据库中的AccountId是未声明的密钥\xe2\x80\x94,它对于每个用户都是唯一的。应使用唯一约束或索引强制执行该信息并将其传达给优化器。

\n

采样统计数据可以方便地避免过多的编译时间,但它们可能会呈现出误导性的图片。除非您特别希望了解统计抽样的高度复杂的世界,否则您应该通过完整扫描来创建或更新统计数据,以获得可重复的高质量结果。

\n

SQL Server 在初始估计之前尝试转换子查询并应用于连接。这并不总是可能的,因此有时会直接导出应用的估计(可能使用内部索引查找)。这通常被建模为一系列点查找。如果您将查询重写为 SQL Server 无法将其转换为联接的应用表单,则您将使用不同的方法获得不同的估计值。这就是野兽的本性。

\n
SELECT\n    U.DisplayName,\n    C.PostId,\n    C.[Text]\nFROM dbo.Users AS U\nCROSS APPLY\n(\n    SELECT\n        C1.PostId,\n        C1.[Text]\n    FROM dbo.Comments AS C1\n    WHERE\n        C1.PostId = U.Reputation\n) AS C\nWHERE\n    U.AccountId = 22547\nOPTION \n(\n    -- Don\'t transform the APPLY to a join\n    QUERYTRACEON 9114\n);\n
Run Code Online (Sandbox Code Playgroud)\n

导出估计值是一个昂贵的过程,因此 SQL Server 希望尽可能避免它。一般来说,没有特别的理由支持一种估计而不是另一种。使用\'n\'种不同(但逻辑上同样合理)的方法完全有可能得出\'n\'种不同的估计。复杂的执行计划有时包含明显矛盾的估计,因为树的不同部分在不同时间使用不同的方法。再说一次,事情就是这样。

\n

在您的示例中,SQL Server 可能不会对应用的内侧进行新的估计,但它确实会执行一些相关计算来估计内侧搜索的成本,以及如果线轴被重绕,它将重绕或反弹多少次。介绍了。在估计“缺失索引”建议的成本节省时,会执行类似的计算。

\n

相关问答和进一步阅读(除非另有说明,由我本人撰写):

\n\n