sqlserver为了确保返回正确的值,或者处于性能上的顾虑,有意不重用缓存在内存里的执行计划,而重新编译执行计划的这种行为,被称为重编译(recompile)。那么引发存储过程重编译的条件有哪一些呢?下面罗列了一些导致重编译(recompile)的条件:
- 对查询所引用的表或视图进行更改(alter table 和 alter view)。
- 对执行计划所使用的任何索引进行更改。
- 对执行计划所使用的统计信息进行更新,这些更新可能是从语句(如 update statistics)中显式生成,也可能是自动生成的。
- 删除执行计划所使用的索引。
- 显式调用 sp_recompile。
- 对键的大量更改(其他用户对由查询引用的表使用 insert 或 delete 语句所产生的修改)。
- 对于带触发器的表,插入的或删除的表内的行数显著增长。
- 使用 with recompile 选项执行存储过程。
- 有些dbcc freeproccache;分离、附加数据库、数据升级也会清除内存里缓存的执行计划
好了,切入到今天我们要关注的问题:临时表的数据变化导致存储过程重编译问题,其实临时表的数据变化导致存储过程重编译实质上是因为临时表的数据变化,导致了临时表统计信息的自动更新,从而引起的重编译。那么触发临时表的统计信息的更新的条件或阀值是什么呢?说来也简单,就是下面一个这个公式(n表示变更前临时表的数据记录数,确切的说是上一次采集统计信息时临时表的记录数)
temporary table
if n
if 6
if n > 500, rt = 500 + 0.20 * n.
有个网友说存储过程中的临时表数据变更的阀值有问题:他的原话如下
if n
if 6
上面这两个区间没有问题。但是大于500的之后,根本就不是变化大于20%之后再重编译。看了他提出的问题,其实我也不是特肯定,毕竟没有实际验证过。实践才是检验整理的唯一标准,那么我们就开始做实验吧,首先准备一下测试环境(microsoft sql server 2008 (rtm) - 10.0.1600.22 (x64) ).脚本如下所示:
use mydba;goif exists(select 1 from sys.sysobjects where id=object_id(n'[dbo].[test]') and objectproperty(id, n'istable')=1 )begin drop table dbo.test;create table test( id int identity(1, 1) , name varchar(40) , constraint pk_test primary key(id))endgoinsert into test values(newid())go 10000create procedure usp_recompile_test(@index int)asbegin create table #t(id int , name varchar(40)); insert into #t select id, name from test where id 500, rt = 500 + 0.20 * n.
由于我第一次执行的是exec dbo.usp_recompile_test 1,那么数据库的记录数为1,那么1+ 6 =7; 也就是上图exec dbo.usp_recompile_test 7时才触发临时表#t的统计信息更新,而为什么是507(7+500=507)呢,因为最后一次统计信息的采集,临时表#t的记录数为7 ,所以7+500=507,是否有点不解,那么你按我这个sql执行一遍,然后用profile跟踪、你会看到下面结果,如果还不太明白,结合截图好好理解一下:
dbcc freeproccache;
exec dbo.usp_recompile_test 2;
exec dbo.usp_recompile_test 6;
exec dbo.usp_recompile_test 7;
exec dbo.usp_recompile_test 8;
如果还没有理解的话,我的表达能力已到极限了,自己再好好琢磨一下吧! 那么接下来才是我们重点想要验证、测试的。
dbcc freeproccache;
exec dbo.usp_recompile_test 501;
此时临时表#t的记录数为501,那么当临时表#t里面的记录数变更了多少时,才会触发统计信息的更新呢? 由于是插入,那么根据公式应该是501 + (500 + 0.2*501) = 1101.2 ,那么应该是1101,即使是1100也不会变化。下面sql server profile可以验证我们的推测
exec dbo.usp_recompile_test 1100;
exec dbo.usp_recompile_test 1101;
如果我们继续使用该存储过程,那么当参数为什么值时才会触发统计信息更新呢? 1101 +(500+0.2*1101)=1821.2,也就是说必须是1821才会触发统计信息更新,下面sql server profile的截图也验证了我们的推测。
exec dbo.usp_recompile_test 1300;
exec dbo.usp_recompile_test 1320;
exec dbo.usp_recompile_test 1321;
exec dbo.usp_recompile_test 1820;
exec dbo.usp_recompile_test 1821;
所以综上述实验验证,sql server 临时表导致存储过程重编译(recompile)的那些阀值确实是正确的,也是没有问题的。当然如有疏漏或不对的地方,敬请指出。