【问题标题】:Eliminate Clustered Index Scan in query消除查询中的聚集索引扫描
【发布时间】:2020-10-18 07:05:29
【问题描述】:

我有这个查询涉及 2 个连接的表。

Select  q.id, 
                         q.LastUpdatedTicksSinceEpoch,
                            q.[Type] [QuoteType],
       q.LatestFormData [FormDataJson], 
                            q.QuoteNumber, 
                            q.QuoteState,
       q.policyId,
                            p.CustomerId, 
                            p.CustomerFullName, 
                            p.CustomerAlternativeEmail,
                            p.CustomerHomePhone,
                            p.CustomerMobilePhone,
                            p.CustomerWorkPhone,
                            p.CustomerPreferredName,
       p.ProductId
                            from Quotes q
                    INNER JOIN  PolicyReadModels p on q.PolicyId=p.id
                    where
                            p.TenantId = @TenantId
                            and p.Environment = @Environment
                           and q.LastUpdatedTicksSinceEpoch > @LastUpdatedTicksSinceEpoch
         and q.QuoteState <> 'Nascent'
                            and q.QuoteNumber is not null
                            and q.IsDiscarded = 0
     ORDER BY q.LastUpdatedTicksSinceEpoch

当我运行它并获得执行计划时,我看到聚集索引扫描 - 我想消除它并使用索引搜索。

如何在此处消除聚集索引扫描?如何构建新的 INDEX?为了添加更多上下文,这将由 12 个并行线程(12 个连接)调用,所以我需要这个快速和优化。

这是我的查询和执行计划:

https://www.brentozar.com/pastetheplan/?id=HkWjdvKDD

【问题讨论】:

  • 为了对其他成员有用,您应该说明您认为此查询可以优化的原因以及您尝试过哪些没有奏效的原因,例如您实现了哪些索引?
  • 对于报价表,只有PK。我将让这个查询在并行线程(12 个连接)中运行。

标签: sql sql-server database database-design


【解决方案1】:

在以下位置创建索引:

create index someIndex on
Quotes (LastUpdatedTicksSinceEpoch)
include (Id, Type, LatestFormData, QuoteNumber, QuoteState, PolicyId, IsDiscarded)
where IsDiscarded = 0
                      

【讨论】:

  • 我得到了这个提升,因为你的答案基本相同(除非 QuoteNumber 有很多 NULL,我认为这不太可能)并且你首先回答了。
  • 我发现 IS NOT NULL 过滤器的处理并不总是那么好,所以我倾向于不经常使用它们。
  • 为了它的价值,我首先选择了这个作为我的答案,但是当我运行它时,它需要很长时间才能完成。添加此子句(QuoteNumber 不为空)实际上对我有用 - (运行 28 秒)。所以我不得不选择 seanb 的答案。
  • 是的,有一个很大的“取决于”选择性以及它是否会在 null 上使用过滤器。我很高兴肖恩的答案被选中。
【解决方案2】:

这个

  • 按您的两个固定值过滤(QuoteNumber NOT NULL 和 IsDiscarded = 0)
  • 按 LastUpdatedTicksSinceEpoch 对数据进行排序(当您在 WHERE 子句中将变量传递给该变量时,它似乎最有可能是您想要排序的变量
  • 包括所有其他相关字段,因此不需要返回到聚集索引(例如,成为覆盖索引)。

请注意,quote.ID 不包括在内,因为它是您的 PK(从我在执行计划中可以看出),因此隐含在索引中。不过,您可以包含它 - 完全不会造成任何损害(例如,索引大小和性能将完全相同)。

CREATE INDEX IX_q ON Quotes 
    (
    LastUpdatedTicksSinceEpoch
    )
INCLUDE
    (
    PolicyId,
    [Type],
    QuoteState,
    LatestFormData,
    QuoteNumber,
    IsDiscarded
    )
WHERE 
    (
    QuoteNumber is not null 
    AND IsDiscarded = 0
    )

注意:我认为这可能是您可以获得的最快速度,但您需要小心,因为表上的索引越多意味着插入、更新和删除越慢。

通常我发现可以在许多地方使用的更短的索引比像这样的非常具体的索引更可取(请记住,如果像LatestFormData这样的某些字段很大,那么这个索引可能会变得臃肿并且对其他索引没有那么有用查询)。

【讨论】:

  • 感谢肖恩。如果行被更新,这会不会很糟糕,假设一个单引号行在一个小时内被更新多达 5 次。此外,是的,LatestFormData 变大(它的 json 值),并且它得到更新 - 我只是删除索引中的 LatestFormData 吗? @seanb
  • OP 的注意事项:索引可以通过两种方式提供帮助。首先,覆盖索引包含您需要的所有字段,因此它们可以在您需要读取较少数据时提供帮助。更重要的是,正确列上的索引允许引擎只寻找适当的点。我强烈建议 Brent Ozar 的 How to think like an SQL engine 1 到 4 或 #5。
  • 一个索引和一个更新很少会成为问题。当您有许多索引并且您更新了数千行时,它才真正开始变得有趣(取决于硬件/内存/等)。关于大字段:如果索引中不包含该字段,则该查询不再被索引覆盖,因此需要返回聚集索引获取数据。一个实用的方法:添加你需要的查询(例如,这个)直到你有太多,然后优化它们。
猜你喜欢
  • 1970-01-01
  • 2019-08-26
  • 2015-01-27
  • 1970-01-01
  • 1970-01-01
  • 2017-06-12
  • 1970-01-01
  • 1970-01-01
  • 2013-11-28
相关资源
最近更新 更多