存储过程通过明确地将查询的可变部分转换为参数来增进执行计划的可重用性。这使执行计划在查询以可变部分的相同或不同值重新提交时可以被重用。存储过程包含一组复杂的SQL语句,使生成存储过程的执行计划的代价有些高。因此,通常重用存储过程的执行计划来代替生成新计划是有利的。但是,有时候现有的计划可能不适用或者在重用期间可能不能提供最佳的处理策略。SQL Server重编译存储过程中的语句来生成一个新的执行计划,以解决这个问题。
一、重编译的好处
存储过程的重编译既有好处又有缺点,有时候,为查询考虑一个新的处理策略而不是重用现有计划可能更经济。特别是表(或对应的统计)中数据的分布变化或者表中添加了新的索引时。SQL Server 2008中的重编译在语句级别上。语句级重编译降低开销是因为只重编译单独的语句,而不是存储过程中的所有语句。而在SQL Server 2005以前重编译方法导致存储过程一次又一次地被完全重编译。
创建一个存储过程并执行,代码如下:
CREATE PROC dbo.getPerson AS SELECT Id,Name,Age FROM PersonHunderThousand WHERE Name = '夏娩好' EXEC dbo.getPerson
目前执行当前存储过程的执行计划如下:
假如我们建了一个覆盖索引之后:
CREATE NONCLUSTERED INDEX IX_Name ON PersonHunderThousand(Name) INCLUDE(Age)
明显,再次执行该查询,使用索引的执行计划更有利:
SQL Server自动重编译了存储过程以有效使用索引。
在这种情况下,花费额外的CPU周期重新编译存储过程建立更好的执行计划是有利的。
SQL Server自动侦测需要重新编译现有计划的条件。SQL Server根据特定的规则确定现有计划需要重新编译的时机。如果存储过程的一个特定实现进入重编译规则的范围(执行计划老化、SET选项变化等),则存储过程将在每次符合重编译要求时重新编译,SQL Server可能不能生成更好的执行计划。
二、重编译存储过程的缺点
在SQL Server Profiler中,SP_Recompile事件用于跟踪语句重编译。也可以使用SQL:StmtRecompile事件,这是存储过程事件。
上面创建的索引是有益于存储过程里面的语句的,这次我们先删除刚刚创建的覆盖索引,然后换成一个对查询语句里无益的索引:
EXEC dbo.getPerson GO CREATE NONCLUSTERED INDEX IX_Name ON PersonHunderThousand(InCome) GO EXEC dbo.getPerson
我们来看SQL Server Profiler监控的输出:
我们看到,重新编译对该存储过程无益。但是不幸地,它进入了导致SQL Server在每次执行时重新编译存储过程的条件范围。这使存储过程缓冲效率降低,并且将CPU周期浪费在重新生成相同的计划上。因此,知道导致存储过程重新编译的条件,并且在实现存储过程时用一切办法避免这些条件是很重要的。当然,这个例子有点牵强,谁没事会一天到晚建索引啊。
三、确认导致重编译的语句
SQL Server可以重新编译存储过程中的单条语句或整个存储过程。因而,为了找到重编译的起因,确认不能重用现有计划的SQL语句是很重要的。
可以使用Profiler工具来跟踪存储过程重编译。也可以使用Profiler工具来确认导致存储过程重编译的语句。
分析存储过程重编译的事件和数据列:
事件 | 数据列 |
SP:Completed | EventClass |
SP:Recompile | TextData |
SP:Starting | EventSubClass |
SP:StmtCompleted(可选) | SPID |
SP:StmtStaring(可选) | StartTime |
考虑下面简单的存储过程:
CREATE PROC dbo.InsertPerson AS CREATE TABLE t1(c1 INT) INSERT INTO t1( c1 )VALUES(42); --数据修改导致重编译
输出如下:
从输出可以看到,有一个重编译事件(SP:Recompile),表示该存储过程经受了重编译。当存储过程第一次执行时,SQL Server编译存储过程并生成一个执行计划。
执行计划值维持在易失性的内存中,它们在SQL Server重启时被抛弃。在服务器重启后的存储过程下一次执行时,SQL Server再次编译存储过程并且生成执行计划。这些编译不被看做存储过程的重编译,因为该计划不存在于缓冲中用于重用,SP:Recompile事件表示计划已经存在但是不能被重用。为了了解那条语句引起的重编译,需要查看SP:Recompile事件中的TextData列,它明确地说明被重编译的语句。还可以使用SP:StmtStarting事件和重编译事件的组合来确认导致重编译的存储过程语句。SP:Recompile事件紧接在SP:StmtStarting事件之后发生表明该存储过程语句导致重编译。使用TextData列更简单,但是在非常复杂的过程中,使用SP:StmtStarting事件可能更有意义。
注意,在存储过程重编译之后,导致重编译的存储过程语句再次启动以执行新的计划。可以使用SP:StmtStarting事件或SP:StmtCompleted事件以确认导致重编译的存储过程语句。