前言
我们提交给SQL Server 的SQL语句都是逻辑上,他们最终都会被编译成物理上的执行计划。生成的执行计划会被缓存在计划缓存中以便下次重用。今天我想谈谈关于计划缓存及其在SQL Server中的副作用
正文
AD-HOC
Ad-hoc这个词来源于拉丁语,意为“for this purpose only”,其中文在wiki上被解释成包含“特设的、特定目的的(地)、即席的、临时的”这么多种含义. 这里可以称之为即席查询或者即时查询。SQL Server为整个SQL语句(包括可能的硬编码参数值)创建一个hash值,并将该hash值用作计划缓存中的查找值。 如果使用此hash值找到执行计划,则计划将被重用,否则将重新编译产生新计划并最终将其缓存到计划缓存中。 比如以下3个查询:
SELECT * FROM Sales.SalesOrderHeader
WHERE CustomerID = 11000
GO
SELECT * FROM Sales.SalesOrderHeader
WHERE CustomerID = 30052
GO
SELECT * FROM Sales.SalesOrderHeader
WHERE CustomerID = 11223
GO
对于这3个查询,因为你提供了一个硬编码的参数值,SQL Server编译产生3个不同的执行计划。 因此,计算出的散列值在这三个查询之间是不同的,并且没有找到缓存的计划。 作为一个副作用,你现在有三个计划,在计划缓存中几乎相同的查询。 这个特定的问题叫做计划缓存污染。
你只是用各种执行计划来污染你的计划缓存,这些计划很难重用(因为硬编码的参数值),而且你正在浪费很多有用的内存,可以被SQL Server中的其他组件使用。 缓存的目标应该始终是高重用次数,而不是很多adhoc SQL语句的情况。
假设你正在为你的SQL语句使用参数值,或者你甚至使用存储过程。在这种情况下,SQL Server可以非常容易地重复使用缓存的执行计划。但即使重复使用缓存的执行计划,也可能引入性能问题。想象一下,SQL Server编译查询的执行计划,该计划必须执行书签查找,因为所使用的非聚集索引不包含您的查询:
在SQL Server中查找书签是相当危险的!
如果您从表中检索一些记录,则书签查找才有意义。如果您已经过了引爆点,那么进行完整的表格式或聚集式索引扫描会更具成本效益。但是如果SQL Server重复使用一个缓存的执行计划,那么这个决定就不再考虑了--SQL Server只是盲目地重用你的计划 - 即使你的性能会很慢!看下面的实际执行计划:
重用缓存计划并不总是一个好主意...
在这里,SQL Server盲目地重复使用书签查找的缓存计划。正如你所看到的估计和实际的行数在这里是完全不同的! SQL Server已经编译和缓存该计划的基础上,从该查询只返回一个行的假设。但实际上,我们从SQL Server获得了1499行。你只是看着一个只有一行被返回的假设下优化的计划 - 想一想。
这里的根本原因是您没有计划稳定性。根据估计的行数,如果您处于引爆点,您将得到一个带书签查找的缓存计划,或者一个表/聚集索引扫描。这是我在客户站点处理性能不佳的SQL Server时遇到的最常见的性能问题。
你怎么能解决这个具体的问题?简单:通过覆盖非聚集索引来避免书签查询。采用这种方法,您已经实现了计划的稳定性,不管第一个提供的输入参数如何,您总是可以获得相同性能的相同计划。如果您有兴趣了解更多关于这个问题的信息,建议您通过SQLpassion Online Academy观看我的一小时视频参数嗅探。