Pro*_*ame 17 sql-server statistics execution-plan sql-server-2016 query-store
每周一次,在过去 5 周内,大约在一天中的同一时间(清晨,可能基于人们开始使用它时的用户活动),SQL Server 2016(AWS RDS,镜像)开始超时很多查询。
所有表上的 UPDATE STATISTICS 总是立即修复它。
在第一次之后,我让它每晚(而不是每周)更新所有表上的所有统计信息,但它仍然发生了,(更新统计信息运行后大约 8 小时,但不是每天运行)。
这最后一次,我启用了查询存储,看看我是否能找到它是哪个特定的查询/查询计划。我想我可以将其缩小为一个:
找到该查询后,我添加了一个推荐索引,该索引在这个不常用的查询中缺失(但它确实触及了很多常用表)。
错误的查询计划正在执行索引扫描(在只有 10k 行的表上)。其他以毫秒为单位返回的查询计划,虽然用于执行相同的扫描。最新的查询计划,在创建新索引后只查找。但即使没有该索引,99% 的情况下,它也会在几毫秒内返回,但是,每周需要超过 40 秒。
这是从 2012 年迁移到 SQL Server 2016 后开始发生的。
DBCC CHECKDB 没有返回错误。
我刚刚添加的索引:
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]
Run Code Online (Sandbox Code Playgroud)
完整的查询文本:
https://pastebin.com/Z5szPBfu(LINQ生成,我可以/应该能够优化所选列,但应该与此问题无关)
Joe*_*ish 17
我将按照与您提出的问题不同的顺序来回答您的问题。
4. 这是更大问题的征兆吗?
SQL Server 2016 中的新基数估计器可能会导致该问题。SQL Server 2012 使用旧版 CE,您在该版本上没有遇到问题。新的基数估计器对您的数据做出不同的假设,并且可以为相同的 SQL 生成不同的查询计划。根据您的查询和数据,使用旧版 CE 执行某些查询时,您可能会体验到更好的性能。因此,数据模型的某些部分可能不是新 CE 的最佳匹配。没关系,但您现在可能需要解决新的 CE。
即使每天更新统计信息,我也会担心不一致的查询性能。需要注意的一件重要事情是收集所有表的统计信息将有效地从缓存中清除所有查询计划,因此您可能会遇到统计问题,或者可能与参数嗅探有关。如果没有关于您的数据模型、数据更改率、统计更新策略、您如何调用代码等的大量信息,很难做出决定。SQL Server 2016 确实提供了一些数据库级别的参数嗅探设置,这可能会有所帮助,但这可能会影响您的整个应用程序,而不仅仅是一个有问题的查询。
我将抛出一个可能导致这种行为的示例场景。你说:
有些用户可能有 1 个权限记录,有些用户可能有 20k。
假设您收集所有表的统计信息,这会消除所有查询计划。根据上面提到的因素,如果当天的第一个查询是针对只有 1 条权限记录的用户,那么 SQL Server 可能会缓存一个计划,该计划适用于具有 1 条记录的用户,但对于具有 20k 条记录的用户却很糟糕。如果当天的第一个查询是针对具有 20k 条记录的用户,那么您可能会得到 20k 条记录的良好计划。当代码针对具有 1 条记录的用户运行时,它可能不是最佳查询,但仍可能在 ms 内完成。这听起来确实像参数嗅探。它解释了为什么您并不总是看到问题,或者为什么有时需要几个小时才能出现。
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. 我如何确保这不会发生在另一个查询/计划中?
我理解你为什么问这个问题,但这是一个非常广泛的问题。我唯一的建议是尝试更好地了解查询计划不稳定的根本原因,验证您是否为您的工作负载创建了正确的索引,并仔细测试和监控您的工作负载。Microsoft 有一些关于如何处理 SQL Server 2016 中新 CE 导致的查询计划回归的一般建议:
将查询处理器升级到最新版本代码的推荐工作流程是:
在不更改数据库兼容性级别的情况下将数据库升级到 SQL Server 2016(保持在先前级别)
在数据库上启用查询存储。有关启用和使用查询存储的更多信息,请参阅使用查询存储监控性能。
等待足够的时间来收集工作负载的代表性数据。
修改数据库兼容级别为130
使用 SQL Server Management Studio,评估兼容性级别更改后特定查询是否存在性能回归
对于存在回归的情况,在查询存储中强制执行先前的计划。
如果存在无法强制执行的查询计划或如果性能仍然不足,请考虑将兼容性级别恢复为之前的设置,然后联系 Microsoft 客户支持。
我并不是说您需要降级到 SQL Server 2012 并重新开始,但所描述的一般技术可能对您有用。
2. 我应该“强制”现在运行良好的计划吗?
这完全取决于你。如果您认为自己的查询计划适用于所有可能的输入参数,并且对查询存储的功能感到满意,并且希望通过强制执行查询计划而感到安心,那么就去做吧。毕竟,强制具有回归的查询计划是微软推荐升级到 SQL Server 2016 策略的一部分。
| 归档时间: |
|
| 查看次数: |
8672 次 |
| 最近记录: |