SQL Server 2016错误查询计划每周锁定数据库一次


16

在过去的5周中,大约每天的同一时间(清晨,可能取决于人们开始使用时的用户活动),每周一次,SQL Server 2016(AWS RDS,已镜像)开始超时查询。

所有表上的UPDATE STATISTICS始终会立即对其进行修复。

第一次之后,我让它每晚(而不是每周)更新所有表上的所有统计信息,但是仍然发生(更新统计信息运行后大约8小时,但并非每天运行)。

上一次,我启用了查询存储,以查看是否可以找到具体的查询/查询计划。我想我可以将其缩小到一个:

错误的查询计划

找到该查询后,我添加了一个推荐索引,该索引在此不常用的查询中丢失了(但它确实涉及很多常用表)。

错误的查询计划正在执行索引扫描(在只有1万行的表上)。不过,其他返回的查询计划(以毫秒为单位)也用于进行相同的扫描。创建新索引后,最新查询计划仅查找。但是,即使没有该索引,也有99%的时间在几毫秒内返回了索引,但是每周要花40秒以上的时间。

从2012年迁移到SQL Server 2016之后,这种情况开始发生。

DBCC CHECKDB不返回任何错误。

  1. 新索引会解决问题,使其不再选择错误的计划吗?
  2. 我应该“强制”现在行之有效的计划吗?
  3. 如何确保其他查询/计划不会发生这种情况?
  4. 这是更大问题的征兆吗?

我刚刚添加的索引:

CREATE NONCLUSTERED INDEX idx_AppointmetnAttendee_AttendeeType
ON [dbo].[AppointmentAttendee] ([UserID],[AttendeeType])

CREATE NONCLUSTERED INDEX [idx_appointment_start] ON [dbo].[Appointment]
(
    [ProjectID] ASC,
    [Start] ASC
)
INCLUDE (   [ID],
    [AllDay],
    [End],
    [Location],
    [Notes],
    [Title],
    [CreatedByID]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

完整查询文字:

https://pastebin.com/Z5szPBfu(由LINQ生成,我可以/应该能够优化所选的列,但是与该问题无关)


我只是注意到对上一个计划没有超时的扫描是在另一个表上,大小相同。约会:11931行,约会:11937行。
专业冠冕堂皇名称

Answers:


16

我将以与您提出问题不同的顺序回答您的问题。

4.这是更大问题的征兆吗?

SQL Server 2016中的新基数估计量可能是导致此问题的原因。SQL Server 2012使用旧版CE,并且您在该版本上没有遇到问题。新的基数估计器对数据进行了不同的假设,并且可以针对同一SQL生成不同的查询计划。对于某些使用旧式CE的查询,您可能会遇到更好的性能,具体取决于您的查询和数据。因此,您的数据模型的某些部分可能不适用于新的CE。没关系,但是您现在可能需要解决新的CE。

即使每天更新统计信息,我也会担心查询性能不一致。要注意的一件事是,收集所有表的统计信息将有效地清除缓存中的所有查询计划,因此您可能会遇到统计问题,或者可能与参数嗅探有关。没有大量有关数据模型,数据更改率,统计信息更新策略,如何调用代码等信息,就很难下定决心。SQLServer 2016确实提供了一些数据库级别的参数嗅探设置,这可能会有所帮助,但这可能会影响整个应用程序,而不仅仅是一个有问题的查询。

我将举一个可能导致这种现象的示例场景。你说:

有些用户可能有1条权限记录,有些则最多为20k。

假设您在所有表上收集统计信息,从而消除了所有查询计划。根据上述因素,如果当天的第一个查询是针对仅具有1个权限记录的用户的,则SQL Server可能会缓存一个计划,该计划对于具有1条记录的用户来说效果很好,但对于具有20k条记录的用户来说效果非常好。如果当天的第一个查询是针对有20k条记录的用户,那么您可能会为20k条记录制定一个好的计划。当代码针对具有1条记录的用户运行时,它可能不是最佳查询,但仍可能以毫秒为单位。听起来确实像参数嗅探。它解释了为什么您并不总是看到问题,或者为什么有时需要几个小时才能显示出来。

1.新索引是否可以解决问题,使其不再选择错误的计划?

我认为您添加的索引之一可以避免此问题,因为通过索引访问所需的数据比对表进行集群索引扫描要便宜,尤其是当扫描无法提前终止时。让我们放大查询计划的糟糕部分:

错误的查询计划

SQL Server的估计,在加入只有一行将返回[Permission][Project]。对于外部输入中的每一行,它将对进行聚集索引扫描[Appointment]。将从该表中扫描所有行,但是只有那些匹配筛选条件的[Start]行将被返回给联接运算符。在联接运算符内,结果会进一步降低。

如果确实只有一行发送到联接的外部输入,则上述查询计划可以。但是,如果联接的基数估计错误并且我们得到了1000行,那么SQL Server将对进行1000次聚集索引扫描[Appointment]。查询计划的性能对估计问题非常敏感。

永不再获取该查询计划的最直接方法是针对该[Appointment]表创建覆盖索引。就像一个索引的东西[ProjectId][Start]应该这样做。看起来这正是[idx_appointment_start]您创建的用于解决该问题的索引。从采摘查询计划阻止SQL服务器的另一种方法是修复基数估计从联接[Permission][Project]。典型的方法包括更改代码,使用旧版CE更新统计信息,创建多列统计信息,为SQL Server提供有关局部变量的更多信息(例如带RECOMPILE提示)或将这些行具体化为临时表。当您需要毫秒级的响应时间或必须通过ORM编写代码时,其中许多技术都不是一种好的方法。

您在其上创建的索引[AppointmentAttendee]不是解决该问题的直接方法。但是,您将获得有关索引的多列统计信息,这些统计信息可能会阻止不良的查询计划。索引可以提供一种更有效的数据访问方式,也可以阻止不良的查询计划,但是我不认为有任何保证,即使打开索引也不会再次发生这种情况[AppointmentAttendee]

3.如何确保其他查询/计划不会发生这种情况?

我理解您为什么要问这个问题,但这是一个非常广泛的问题。我唯一的建议是尝试更好地了解查询计划不稳定的根本原因,验证您是否为工作负荷创建了正确的索引,并仔细测试和监视工作负荷。对于如何处理由SQL Server 2016中的新CE引起的查询计划回归,Microsoft提供了一些一般性建议

将查询处理器升级到最新版本代码的推荐工作流程是:

  1. 在不更改数据库兼容性级别的情况下将数据库升级到SQL Server 2016(保持在先前级别)

  2. 在数据库上启用查询存储。有关启用和使用查询存储的更多信息,请参阅通过使用查询存储监视性能。

  3. 等待足够的时间来收集工作负载的代表性数据。

  4. 将数据库的兼容性级别更改为130

  5. 使用SQL Server Management Studio,评估兼容性级别更改后,特定查询是否存在性能下降

  6. 对于存在回归的情况,请在查询存储中强制使用先前的计划。

  7. 如果存在无法强制执行的查询计划或性能仍然不足,请考虑将兼容性级别恢复为先前的设置,然后与Microsoft客户支持联系。

我并不是说您需要降级到SQL Server 2012并重新开始,但是所描述的常规技术可能对您有用。

2.我应该“强制”现在行之有效的计划吗?

这完全取决于您。如果您认为自己有一个对所有可能的输入参数都适用的查询计划,对查询存储的功能感到满意,并且希望强制执行查询计划可以放心,那就继续吧。毕竟,强制具有回归的查询计划是Microsoft建议的SQL Server 2016升级策略的一部分。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.