【问题标题】:SQL Server choosing inefficient execution planSQL Server 选择低效的执行计划
【发布时间】:2016-12-19 01:41:21
【问题描述】:

我有一个在某些情况下运行的查询,它的执行计划“过于简单”,但实际上速度很慢(3-5 秒)。查询是:

SELECT DISTINCT Salesperson.*
FROM Salesperson
    INNER JOIN SalesOrder on Salesperson.Id = SalesOrder.SalespersonId
    INNER JOIN PrelimOrder on SalesOrder.Id = PrelimOrder.OrderId
    INNER JOIN PrelimOrderStatus on PrelimOrder.CurrentStatusId = PrelimOrderStatus.Id
    INNER JOIN PrelimOrderStatusType on PrelimOrderStatus.StatusTypeId = PrelimOrderStatusType.Id
WHERE 
    PrelimOrderStatusType.StatusTypeCode = 'Draft'
    AND Salesperson.EndDate IS NULL

缓慢的执行计划看起来像:

立即脱颖而出的是实际行数/执行数显着高于各自的估计值:

如果我删除 Salesperson.EndDate IS NULL 子句,则运行更快的并行执行计划:

如果我删除 DISTINCT 关键字,类似的执行计划也会运行得非常快。

据我所知,优化器似乎根据其不正确的估计决定查询的运行成本不会很高,因此不选择并行计划。但我一生都无法弄清楚为什么它选择了不正确的计划。我检查了我的统计数据,它们都是应有的。我已经在 SQL Server 2008 到 2016 中进行了测试,结果相同。

【问题讨论】:

  • @BrockAdams 完全忘记了这些,抱歉 ^_^ 感谢提醒

标签: sql sql-server query-optimization sql-execution-plan query-performance


【解决方案1】:

SELECT DISTINCT 很贵。所以,最好避免它。像这样的:

SELECT sp.*
FROM Salesperson sp
WHERE EXISTS (SELECT 1
              FROM SalesOrder so INNER JOIN
                   PrelimOrder po 
                   ON so.Id = po.OrderId INNER JOIN
                   PrelimOrderStatus pos
                   ON po.CurrentStatusId = pos.Id INNER JOIN
                   PrelimOrderStatusType post
                   ON pos.StatusTypeId = post.Id
              WHERE sp.Id = so.SalespersonId AND
                    post.StatusTypeCode = 'Draft'
             ) AND
      sp.EndDate IS NULL;

注意:SalesPerson(EndDate, Id) 上的索引会有所帮助。

【讨论】:

  • 不幸的是,该查询的执行计划与我的初始查询相同。
【解决方案2】:

正如@Gordon Linoff 已经说过的,DISTINCT 通常对性能来说是个坏消息。通常这意味着你积累了太多的数据,然后将它们压缩成一个更紧凑的集合。如果可能,最好在整个过程中保持较小。

此外,带有索引扫描的查询计划比带有索引查找的查询计划更快,这有点违反直觉。似乎(在这种情况下)并行性弥补了这一点。您可以尝试使用 Cost Threshold For Parallelism Option,但请注意这是服务器范围的设置! (再说一次,在我看来,对于我个人遇到的大多数用例来说,默认值 5 相当高;这些天 CPU 很多,时间仍然不够 =)。

有点长,但我想知道您是否可以将查询“拆分”为 2,从而消除服务器的(一小部分)猜测。我在这里假设StatusTypeCode 是独一无二的。 (也验证变量的数据类型!)

DECLARE @StatusTypeId int

SELECT @StatusTypeId = Id 
  FROM PrelimOrderStatusType 
 WHERE StatusTypeCode = 'Draft'

SELECT Salesperson.*
  FROM Salesperson
 WHERE Salesperson.EndDate IS NULL
   AND EXISTS ( SELECT * 
                  FROM SalesOrder 
                    ON SalesOrder.SalespersonId = Salesperson.Id
                  JOIN PrelimOrder 
                    ON PrelimOrder.OrderId = SalesOrder.Id 
                  JOIN PrelimOrderStatus 
                    ON PrelimOrderStatus.Id = PrelimOrder.CurrentStatusId
                   AND PrelimOrderStatus.StatusTypeId = @StatusTypeId)

如果没有帮助,您能否给出正在使用的索引的定义?

【讨论】:

  • 我已经使用 EXISTS 而不是 DISTINCT 测试了该查询,但它产生了一个类似的查询计划,但在执行计划中仍然存在相同的差异。速度有所提高,但这是由于删除了 PrelimOrderStatusType 上的连接。我之前尝试将成本阈值设置为 1,但在这种情况下没有效果。
猜你喜欢
  • 1970-01-01
  • 2014-01-31
  • 2013-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-30
  • 2011-04-12
  • 2021-12-09
相关资源
最近更新 更多