主动防止查询随机变慢的良好做法

lio*_*ori 0 sql-server query-performance

我们支持基于 SQL Server 的内部应用程序的多种部署。

我们现在遇到过几次问题,一些曾经快速而小的查询突然变得非常缓慢或不稳定,而不一定会产生更多的数据或在更大的数据集上运行。

每次发生这种情况时,都是突然开始缓慢运行的不同查询。每当这种情况发生时,我们必须花一些开发时间来调试问题,可能会再次编写查询并与以前的版本进行基准测试等等。 然而,原始查询通常可以正常工作多年,而且我们事先没有迹象表明它的执行时间可能会爆炸。

有哪些好的做法可以防止查询随机变慢?

我所追求的不是“如何修复我们已经观察到很慢的查询”,而是首先如何降低查询变慢的可能性——一种主动的方法,而不是被动的。我需要一种方法来强制 SQL Server 不要尝试过多地优化查询:我真的不需要快速,我需要一致。如果有一些选择让 SQL Server 在构建查询计划时更加保守,而不是试图利用数据分布等,我会追求它们。

J.D*_*.D. 7

参数嗅探

如果数据在查询速度快到速度变慢之间完全没有变化,那么您可能遇到了参数嗅探问题。(请注意,即使数据发生更改,这种情况也可能发生,但它也不需要数据发生更改。)当在查询中使用参数(例如在存储过程中)时,会发生这种情况。

发生这种情况的原因是因为在生成执行计划时第一次运行过程时,它针对提供给过程参数的第一组值进行了优化,然后该查询计划被缓存并重新用于将来的运行相同的过程(无论什么值再次作为参数传入)。

当原始缓存计划被重用时,具有不同参数的过程的后续执行可能并不总是具有最佳的执行计划。这取决于一些事情,主要是数据的基数。一般来说,发生在该基数的参数使用时执行计划最初生成并缓存在变化了很多基数中的程序通过在后续执行的参数值。

例如,假设 Cars 表有 1,000,000 行,在 999,999 行上,制造商是本田,但在第一行中,制造商是宝马。为 BMW 查找并返回 1 行的最佳执行计划肯定与为本田查找并返回 999,999 行不同。表中值的唯一性是两个值之间的基数差异。但是对于存储过程,因为执行计划在第一次运行时被缓存,如果第一次运行该过程时将 BMW 传递到参数中,则将使用对本田来说次优的相同计划。(例如,在这种情况下,索引搜索操作对于宝马来说性能最好,但对于本田来说会很慢,如果使用索引扫描会更好。)


基数估计问题

前面讨论的参数嗅探问题通常是基数估计问题的结果(即它是另一个问题的子问题)。但是即使没有参数嗅探问题,您也可能遇到基数估计问题。

用最简单的术语来说,基数是表中列的特定值存在的唯一性或行数。JOIN、WHERE、HAVING 子句谓词的基数会影响 SQL Server 引擎选择查找和过滤数据的方式。这是因为不同的操作对于返回基数较低的内容(包含该值的记录数量较少,因此非常独特)与基数较高的内容(大量记录包含该值,因此不是很独特)的性能更高。

例如,当预期为您的 JOIN、WHERE、HAVING 子句返回大量记录时,扫描数据的操作性能可能更高,而不是查找哪个对少量行的性能更高。因此,不同的基数导致查找和过滤数据的不同操作,最终导致不同的执行计划。

在 JOIN、WHERE 或 HAVING 子句中使用函数(尤其是非确定性函数)是导致基数估计错误的一种常见情况。这是因为它使引擎在执行计划生成过程中准确确定这些函数值的基数的能力变得复杂。其他会使您的谓词复杂化的事情是使用算术运算符,以及导致隐式转换的不同数据类型字段之间的比较(例如,如果一个字段是 NVARCHAR 并且与另一个是 VARCHAR 的字段进行比较)。

如果您的查询在小表上运行或仅返回少量数据,那么您可能没有意识到您有潜在的基数估计问题。有时,它们最初不会公开自己,直到其中一个基础表中的少量数据发生更改之后。这绝对是您正在经历的事情,但如果没有您的执行计划,我们将无法确定。


过时的统计数据

如果不是参数嗅探问题,并且您的数据在快速和慢速运行之间发生了变化,那么您可能会遇到的其他事情是过时的Statistics。(尽管这种情况不太可能发生,除非您的数据发生了很大变化,或者您的服务器被错误地配置为不自动更新统计信息。

SQL Server 定期存储有关表中值的基数统计信息,以便它可以在生成执行计划时对要使用的操作做出准确的决策。这是为了确保生成最佳的执行计划

在变化相当频繁的表上,它们的统计数据有时会变得陈旧,并可能导致为它们生成次优的执行计划,因此需要手动更新这些统计数据


索引不佳

索引是一种以高效的逻辑数据结构B-Tree将数据存储在表中的方法。如果没有聚集索引,您的表存储在其中的逻辑数据结构是Heap。一个聚集索引转换是B树是用于搜索了很多更有效率。该聚集索引决定了你的整个表本身的实际顺序。

非聚集索引将表中特定字段的副本存储到它们自己的B 树中,这些B 树根据这些字段本身进行排序。它们还存储对表主键的引用,以防他们需要从表中查找未存储在非聚集索引本身中的更多信息。

如果您的表缺少适当的索引,则您的查询可能会在使用少量数据时足够快,但随着表的增长而继续变慢。此外,如果您在最初编写查询时拥有适当的索引,并且有人添加、更改或删除其中一个索引,则可能会生成一个次优的不同执行计划。(是的,像在表上添加额外索引这样简单的事情可能会导致现有的最佳执行计划不再用于特定查询,而是可以使用次优计划。这是查询优化器的错SQL Server 引擎和该表的统计信息。)


其他资源 - 特定主题

  1. RedGate -执行计划基础- 这是对什么是执行计划、它们是如何创建的概述的一个很好的开始,它将帮助您了解它们的目的以及如何使用它们来调试性能问题。我发现执行计划可以回答某人遇到的 95% 的问题,甚至可以主动告诉您一个目前可能不会影响性能但肯定会影响未来性能的问题。

  2. Brent Ozar - How Execution Plans Are Made - 这是一个很好的视频,提供了一些关于如何形成执行计划的示例的视觉效果,并帮助您像 SQL Server 引擎一样思考。

  3. Brent Ozar -观看 Brent Tune Queries - 这是一个很好的视频,用于了解查询优化时要查找的关键内容。您将更深入地了解如何使用执行计划来帮助您确定性能问题的原因。您甚至可以在编写查询后主动检查其中的一些内容。

  4. Brent Ozar -再次观看 Brent Tune Queries - 这是 Brent 再次进行基本查询调优,因此虽然不是完全相同的视频,但概念是多余的(以一种好的方式)帮助您了解应该做的事情,甚至在您编写查询后主动验证其性能在将来是否适用。

  5. SQL Server Central -统计和基数估计-基数估计是一件非常重要的事情,需要理解(并且比理解诸如正确索引之类的基本事情稍微复杂一些)。本文将向您介绍它是什么,以及它在 SQL Server 中的用途。我强烈建议您也查看其他资源(当我找到其他好的答案时,我会继续更新我的答案)。你也可以通过查看执行计划来主动检查很多次在你写完之后立即查询你的查询。您可以在计划中查看一些特定的内容,例如估计行数与实际行数之间的距离(通常是幅度偏差),这表明您何时遇到基数估计问题。


其他资源 - 一般

(将@LadyBug1 的评论强化到我的历史参考答案中,因为它们是很好的建议。)

这些参考资料通常是了解 SQL Server、它的引擎以及如何以最佳方式为您的数据库进行开发的好地方。他们是 SQL 社区中一些最受尊敬的成员:

  1. 布伦特·奥扎尔
  2. 肯德拉·利特尔
  3. 保罗兰德尔
  4. Aaron Bertrand 的任何博客:SQLPerformance.comSQLBlog.orgMSSQLTips.com
  5. 埃里克·达林
  6. 乔纳森·凯哈亚斯
  7. 保罗怀特
  8. 格兰特·弗里奇
  9. RedGate 的简单谈话博客

如果您可以使用特定示例(查询、用于参数的数据和执行计划)更新您的答案,我们可以更明确地告诉您问题是什么并指出如何确定它。