SQL Server选择低效的执行计划

时间:2016-12-19 01:41:21

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

我有一个查询在某些情况下运行过度简化'执行计划实际上变得非常慢(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

慢速执行计划如下:

Slow Execution Plan

直接突出的是,实际的行数/执行次数明显高于各自的估算值:

IX_SalesOrder_SalespersonId

UX_PrelimOrder_OrderId

UX_PrelimOrderStatus_Id

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

Fast Execution Plan

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

根据我可以收集的信息,优化人员似乎根据其不正确的估算确定查询的运行成本不高,因此不会选择并行计划。但我不能为我的生活弄清楚为什么选择错误的计划。我检查了我的统计数据,他们都是应有的。我已经在SQL Server 2008到2016中进行了测试,结果相同。

2 个答案:

答案 0 :(得分: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)上的索引会有所帮助。

答案 1 :(得分:0)

正如@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)

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