为什么 SQL Server 为其限定的表的每一行运行一个子查询?

Cap*_*ock 8 optimization database-tuning sql-server-2008-r2 subquery

此查询在 ~21 秒内运行(执行计划):

select 
    a.month
    , count(*) 
from SubqueryTest a 
where a.year = (select max(b.year) from SubqueryTest b)
group by a.month
Run Code Online (Sandbox Code Playgroud)

当子查询被变量替换时,它会在 <1 秒内运行(执行计划):

declare @year float
select @year = max(b.year) from SubqueryTest b
select 
    month
    , count(*) 
from SubqueryTest where year = @year group by month
Run Code Online (Sandbox Code Playgroud)

从执行计划来看,“select max...”子选择对“SubqueryTest a:”中的数百万行中的每一行都运行,这就是为什么它需要这么长时间。

我的问题:由于子选择是标量、确定性且不相关,为什么查询优化器不执行我在第二个示例中所做的操作并运行子查询一次,存储结果,然后将其用于主查询?我确定我对 SQL Server 的理解只是一个漏洞,但我真的很想帮助填补它 - 用谷歌几个小时没有帮助。

该表刚超过 1GB,有近 2800 万条记录:

CREATE TABLE SubqueryTest(
  [pk_id] [int] IDENTITY(1,1) NOT NULL
  , [Year] [float] NULL
  , [Month] [float] NULL PRIMARY KEY CLUSTERED ([pk_id] ASC))

CREATE NONCLUSTERED INDEX idxSubqueryTest ON SubqueryTest ([Year] ASC)
Run Code Online (Sandbox Code Playgroud)

Mar*_*ith 6

慢速计划计算MAX外部查询中的每一行。

事实上,它根本没有明确计算它。

它给出了一个类似于

WITH CTE
     AS (SELECT TOP(1) WITH TIES *
         FROM   SubqueryTest
         WHERE year IS NOT NULL
         ORDER  BY year desc)
SELECT month,
       count(*)
FROM   CTE
GROUP  BY month 
Run Code Online (Sandbox Code Playgroud)

慢速计划(估计行数)

在此处输入图片说明

您有一个非覆盖索引,year asc因此它会向后扫描以获取第一年的行(由于隐式IS NOT NULL谓词而显示为搜索)。

不幸的是,它似乎并没有区分估计行数TOP 1TOP 1 WITH TIES何时估计行数。

在这种情况下,它会产生巨大的差异。(估计 2 个键查找 vs 实际 4,424,803)所以你得到了一个不合适的计划。

慢计划(实际行数)

在此处输入图片说明

您可以考虑将作为键或包含列添加month到索引中year以使索引覆盖。将它添加为辅助键列的好处是,它可以在没有额外排序的情况下提供给流聚合(尽管对于 12 个不同的值,哈希聚合无论如何都可以)。

对于绝大多数查询来说,这种非选择性列上的非覆盖索引确实毫无用处。该索引被“快速”计划完全忽略,该计划最终对整个表进行并行扫描并评估所有 27,445,400 行的谓词(优先执行大量查找)。

在此处输入图片说明