执行计划的缓存和重新使用

SQL Server 有一个用于存储执行计划和数据缓冲区的内存池。池内分配给执行计划或数据缓冲区的百分比随系统状态动态波动。内存池中用于存储执行计划的部分称为过程缓存。

SQL Server 执行计划包含下列主要组件:

  • 查询计划

    执行计划的主体是一个重入的只读数据结构,可由任意数量的用户使用。这称为查询计划。查询计划中不存储用户上下文。内存中查询计划副本永远不超过两个:一个副本用于所有的串行执行,另一个用于所有的并行执行。并行副本覆盖所有的并行执行,与并行执行的并行度无关。

  • 执行上下文

    每个正在执行查询的用户都有一个包含其执行专用数据(如参数值)的数据结构。此数据结构称为执行上下文。执行上下文数据结构可以重新使用。如果用户执行查询而其中的一个结构未使用,将会用新用户的上下文重新初始化该结构。

执行上下文,同一查询,不同文字

在 SQL Server 中执行任何 SQL 语句时,关系引擎将首先查看过程缓存中是否有用于同一 SQL 语句的现有执行计划。SQL Server 将重新使用找到的任何现有计划,从而节省重新编译 SQL 语句的开销。如果没有现有执行计划,SQL Server 将为查询生成新的执行计划。

SQL Server 有一个高效的算法,可查找用于任何特定 SQL 语句的现有执行计划。在大多数系统中,这种扫描所使用的最小资源比通过重新使用现有计划而不是编译每个 SQL 语句所节省的资源要少。

该算法将新的 SQL 语句与缓存内现有的未用执行计划相匹配,并要求所有的对象引用完全合法。例如,在下列 SELECT 语句中,第一个语句与现有计划不匹配,而第二个语句匹配:

SELECT * FROM Person;

SELECT * FROM Person.Person;

从过程缓存中删除执行计划

只要过程缓存中有足够的存储空间,执行计划将保留在其中。如果存在内存不足的情况,数据库引擎将使用基于开销的方法来确定从过程缓存中删除哪些执行计划。要做出基于开销的决策,数据库引擎将根据以下因素对每个执行计划增加和降低当前开销变量。

当某个用户进程将执行计划插入缓存中时,该用户进程会将当前开销设置为等于原始查询编译开销;对于即席执行计划,该用户进程会将当前开销设置为零。以后,用户进程每次引用执行计划时,都会将当前开销重置为原始编译开销;对于即席执行计划,用户进程会增加当前开销。对于所有计划而言,当前开销的最大值就是原始编译开销。

如果存在内存不足的情况,数据库引擎将会把执行计划从过程缓存中删除以进行响应。为了确定删除哪些执行计划,数据库引擎会重复检查每个执行计划的状态并将删除当前开销为零的执行计划。如果存在内存不足的情况,当前开销为零的执行计划不会自动被删除,而只有在数据库引擎检查该执行计划并发现其当前开销为零时,才会删除该计划。当检查执行计划时,如果当前没有查询使用该计划,则数据库引擎将降低当前开销以将其推向零。

数据库引擎会重复检查执行计划,直至删除了足够多的执行计划,以满足内存需求为止。如果存在内存不足的情况,执行计划可多次对其开销进行增加或降低。如果内存不足的情况已经消失,数据库引擎将不再降低未使用执行计划的当前开销,并且所有执行计划都将保留在过程缓存中,即使其开销为零也是如此。

数据库引擎使用资源监视器和用户线程从过程缓存中释放内存,以响应内存不足。资源监视器和用户线程可以检查并发运行的执行计划以降低每个未使用执行计划的当前开销。如果存在全局内存不足的情况,资源监视器将会从过程缓存中删除执行计划。它释放内存以强制实施系统内存、进程内存、资源池内存和所有缓存最大大小的策略。

所有缓存的最大大小是缓存池大小的一个函数,不能超出最大服务器内存的大小。有关配置最大服务器内存的详细信息,请参阅 sp_configure (Transact-SQL) 中的 max server memory 设置。

当存在单一高速缓存不足的情况时,用户线程将会从过程缓存中删除执行计划。它们强制实施最大单一缓存大小和最大单一缓存条目数的策略。

以下示例说明会从过程缓存中删除哪些执行计划:

  • 一个经常被引用的执行计划,该计划的开销从未等于零。除非遇到内存不足和当前开销为零的情况,否则该计划保留在过程缓存中,不会被删除。

  • 插入的一个即席执行计划,并且在内存不足情况出现之前没有再次引用该计划。由于即席计划在初始化后当前开销为零,因此在数据库引擎检查执行计划时,会发现当前开销为零,于是从过程缓存中删除该计划。如果不存在内存不足的情况,当前开销为零的即席执行计划将保留在过程缓存中。

若要从缓存中手动删除单个计划或所有计划,请使用 DBCC FREEPROCCACHE (Transact-SQL)

重新编译执行计划

根据数据库新状态的不同,数据库中的某些更改可能导致执行计划效率降低或无效。SQL Server 将检测到使执行计划无效的更改,并将计划标记为无效。此后,必须为执行查询的下一个连接重新编译新的计划。导致计划无效的情况包括:

  • 对查询所引用的表或视图进行更改(ALTER TABLE 和 ALTER VIEW)。

  • 对执行计划所使用的任何索引进行更改。

  • 对执行计划所使用的统计信息进行更新,这些更新可能是从语句(如 UPDATE STATISTICS)中显式生成,也可能是自动生成的。

  • 删除执行计划所使用的索引。

  • 显式调用 sp_recompile

  • 对键的大量更改(其他用户对由查询引用的表使用 INSERT 或 DELETE 语句所产生的修改)。

  • 对于带触发器的表,插入的删除的表内的行数显著增长。

  • 使用 WITH RECOMPILE 选项执行存储过程。

为了使语句正确,或要获得可能更快的查询执行计划,大多数都需要进行重新编译。

在 SQL Server 2000 中,只要批处理中的语句导致重新编译,就会重新编译整个批处理,无论此批处理是通过存储过程、触发器、即席批查询,还是通过预定义的语句进行提交。在 SQL Server 2005 和更高版本中,只会重新编译批处理中导致重新编译的语句。由于这种差异,SQL Server 2000 和更高版本中的重新编译计数不可比较。另外,由于 SQL Server 2005 和更高版本扩展了功能集,因此它们具有更多类型的重新编译。

语句级重新编译有助于提高性能,因为在大多数情况下,只有少数语句导致了重新编译并造成相关损失(指 CPU 时间和锁)。因此,避免了批处理中其他不必重新编译的语句的这些损失。

SQL Server Profiler SP:Recompile 跟踪事件报告语句级重新编译。此跟踪事件在 SQL Server 2000 中仅报告批处理重新编译。此外,将填充此事件的 TextData 列。因此,已不再需要 SQL Server 2000 中必须跟踪 SP:StmtStartingSP:StmtCompleted 以获取导致重新编译的 Transact-SQL 文本的做法。

跟踪事件 SQL:StmtRecompile 报告语句级重新编译。此跟踪事件可用于跟踪和调试重新编译。SP:Recompile 仅针对存储过程和触发器生成,而 SQL:StmtRecompile 则针对存储过程、触发器、即席批查询、使用 sp_executesql 执行的批处理、已准备的查询和动态 SQL 生成。

SP:RecompileSQL:StmtRecompileEventSubClass 列都包含一个整数代码,用以指明重新编译的原因。下表包含每个代码号的意思。

EventSubClass 值

说明

1

架构已更改。

2

统计信息已更改。

3

编译已延迟。

4

SET 选项已更改。

5

临时表已更改。

6

远程行集已更改。

7

FOR BROWSE 权限已更改。

8

查询通知环境已更改。

9

分区视图已更改。

10

游标选项已更改。

11

已请求 OPTION (RECOMPILE)。

注意注意

当 AUTO_UPDATE_STATISTICS 数据库选项被设置为 ON 时,如果查询以表或索引视图为目标,而自上次执行后,表或索引视图的统计信息已更新或基数已发生很大变化,查询将被重新编译。此行为适用于标准用户定义表、临时表以及由 DML 触发器创建的 inserted 和 deleted 表。如果过多的重新编译影响到查询的性能,请考虑将此设置更改为 OFF。当 AUTO_UPDATE_STATISTICS 数据库选项设置为 OFF 时,不会因统计信息或基数的更改而发生任何重新编译,但是,由 DML INSTEAD OF 触发器创建的 inserted 和 deleted 表除外。因为这些表是在 tempdb 中创建的,因此,是否重新编译访问这些表的查询取决于 tempdb 中 AUTO_UPDATE_STATISTICS 的设置。请注意,在 SQL Server 2000 中,即使此设置为 OFF,查询仍然会基于 DML 触发器 inserted 和 deleted 表的基数变化进行重新编译。有关禁用 AUTO_UPDATE_STATISTICS 的详细信息,请参阅使用统计信息提高查询性能