繁体   English   中英

SQL SERVER 为两个相似查询生成不同且未优化的执行计划

[英]SQL SERVER generates different and unoptimized execution plan for two similar queries

使用 SQL SERVER 执行以下两个查询

SELECT TOP(10) [c].[Name] AS [I0], [c].[Surname] AS [I1],(
    SELECT MAX([r].[Date])
    FROM [Presences].[Regs] AS [r]
    WHERE ([c].[Id] = [r].[ColId]) AND [r].[ColId] IS NOT NULL) AS [I7], [c].[Id] AS [I10]
FROM [Presences].[Cols] AS [c]
SELECT TOP(10) [c].[Code] AS [I0], [c].[Description] AS [I1], (
    SELECT MAX([r].[Date])
    FROM [Presences].[Regs] AS [r]
    WHERE ([c].[Id] = [r].[CantId]) AND [r].[CantId] IS NOT NULL) AS [I9], [c].[Id] AS [I10]
FROM [Presences].[Cants] AS [c]

第二个查询的评估时间不到 1 秒,但第一个查询需要 20 多秒。

事实上,第一个生成的执行计划非常不同:

第一个查询的错误执行计划

如果与第二个相比:

第二次查询的好执行计划

我不清楚为什么不选择索引搜索。

这些是在表 [Regs] 上声明的索引:

CREATE NONCLUSTERED INDEX [IX_Regs_Date] ON [Presences].[Regs]
(
    [Date] ASC
)

CREATE NONCLUSTERED INDEX [IX_Regs_ColId] ON [Presences].[Regs]
(
    [ColId] ASC
)

CREATE NONCLUSTERED INDEX [IX_Regs_CantId] ON [Presences].[Regs]
(
    [CantId] ASC
)

表 [Cols] 有大约 600 行,表 [Cants] 有 21000。

有趣的事实是以下查询(使用 FORCESEEK)生成了正确的执行计划:

SELECT TOP(10) [c].[Name] AS [I0], [c].[Surname] AS [I1],(
    SELECT MAX([r].[Date])
    FROM [Presences].[Regs] AS [r]
    WITH (FORCESEEK)  
    WHERE ([c].[Id] = [r].[ColId]) AND [r].[ColId] IS NOT NULL) AS [I8]
FROM [Presences].[Cols] AS [c]
ORDER BY [c].[Name], [c].[Surname]

但我无法指定此提示,因为查询是使用 ORM 生成的。

如果您需要更多信息,我很乐意提供。

您的问题查询是

SELECT TOP(10) c.Name                      AS I0,
               c.Surname                   AS I1,
               (SELECT MAX(r.Date)
                FROM   Presences.Regs AS r
                WHERE  ( c.Id = r.ColId )) AS I7,
               c.Id                        AS I10
FROM   Presences.Cols AS c 
ORDER BY c.Name, c.Surname

在好计划和坏计划中,顶部是相同的

在此处输入图像描述

它扫描Cols表,按Name, Surname排序,然后继续计算Presences.Regs中的MAX(Date) ,其中r.ColId与外行的相应c.Id匹配。

理想的索引是ColId, Date上的索引 - 因此它可以查找索引并读取该ColId的最后一个。

你没有这个索引,所以它有两个选择

在此处输入图像描述

在此处输入图像描述

  1. 扫描IX_Regs_Date - 这是按日期顺序排列的,因此它可以在找到与c.Id = r.ColId匹配的第一行后立即停止扫描
  2. 寻找IX_Regs_ColId 获取与谓词匹配的所有行,然后将它们聚合以找到MAX

对于选项 1,它估计每次扫描平均只需读取283行,然后才能找到第一个匹配c.Id = r.ColId的行。 实际上,每次执行读取1,358,719次(并且有 10 次执行,因此对应的 1358 万次键查找)。 表中有1,517,230行,因此看起来好像出于某种原因,所有匹配联接的行都聚集在表中的较晚日期。

解决此问题的最简单方法是为其提供ColId, Date的理想索引 - 这涵盖了查询,因此即使在“好”计划中也可以删除查找,并且是明显的最佳选择,因此将消除 SQL 的诱惑服务器到 select 的“坏”计划。

--Suggested replacement for IX_Regs_ColId
CREATE NONCLUSTERED INDEX [IX_Regs_ColId_Date] ON [Presences].[Regs]
(
    [ColId] ASC,
    [Date] ASC
)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM