如何track存储过程的编译次数

本文通过实验深入探讨了SQL Server中存储过程的重编译机制,并详细解析了plan_generation_num字段的含义及其计算方式。此外,还介绍了如何通过特定脚本找到重编译次数较多的存储过程。

转载自此处

有个script我们很熟悉,是用来去查找当前SQL Server中哪些存储过程变重编译的次数最多的:

--Gives you the top 25 stored procedures that have been recompiled.

select top 25 sql_text.text, sql_handle, plan_generation_num, execution_count,

dbid, objectid

into DMV_Top25_Recompile_Commands

from sys.dm_exec_query_stats a

cross apply sys.dm_exec_sql_text(sql_handle) as sql_text

where plan_generation_num >1

order by plan_generation_num desc

go

那么,这个脚本究竟是记录什么情况下的存储过程recomile呢?

我们在SQL Server上创建一个这样的store procedure

create proc aaa

as

select plan_generation_num,* FROM DMV_Top25_Recompile_Commands where plan_generation_num > 2

然后准备好用这个脚本来返回plan_generation_num的值

select top 25 sql_text.text, sql_handle, plan_generation_num, execution_count,

dbid, objectid

from sys.dm_exec_query_stats a

cross apply sys.dm_exec_sql_text(sql_handle) as sql_text

where sql_text.text like '%aaa%'

order by plan_generation_num desc

Exec aaa之后的脚本返回结果:

这里的第六行结果集就是我们的存储过程aaa。这时的plan_generation_num值显示为1.

接下来我们mark recompile

sp_recompile aaa

然后再次执行exec aaa

使用脚本查询:

这里看到存储过程重编译以后,plan_generation_num的值并没有增加。

那为什么我们还会使用这样的脚本来返回重编译次数很多的存储过程呢?

接下来我们再次将存储过程markrecompile,然后直接使用脚本查询:

这时,我们发现该存储过程的plantext已经从DMV中移除了。看起来sp_recompile会直接将cache中缓存的执行计划和语句直接标识成不可用。因此DMV中就没有相关的记录了。

这就是说,存储过程标识重编译这种模式导致的重编译,从DMV里面是没有办法跟踪的。

那么从性能监视器的计数器sp recompilation/sec”里面能不能跟踪到呢?

我们反复执行:

sp_recompile aaa

exec aaa

性能监视器中一直显示为0

那么plan_generation_num的值究竟是什么含义呢?BOL中的解释很简单:

A sequence number that can be used to distinguish between instances of plans after a recompile.

中文版的含义为:可用于在重新编译后区分不同计划实例的序列号。

这里并没有说明如何去计算的序列号。我们从另一篇英文的blog中找到了更加详细的说明:

There are a lot of interesting columns in P and S, especially in S, and here I will only discuss what I have learned about plan_generation_num in S. SQL Server 2005 treats the compiled plan for a stored procedure as an array of subplans, one for each query statement. If an individual subplan needs recompilation, it does so without causing the whole plan to recompile. In doing so, SQL Server increments the plan_generation_num on the subplan record to be 1 + MAX(plan_generation_num for all subplans). The general distribution of plan_generation_num among all subplans for a given plan is such that it has multiple of 1's and distinct numbers > 1. That is because all subplans start with 1 as their plan_generation_num. Appendix A is the query for learning plan_generation_num.

http://lfsean.blogspot.com/2008/02/understanding-sql-plangenerationnum.html

这部分说明简单的来说,就是只要存储过程中有一条语句发生重编译,这个plan_generation_num值就会+1.这里并没有说是整个存储过程重编译的时候,这个值会+1.

接下来我们修改测试存储过程aaa

Alter TABLE aaa_table(

[text] [nvarchar](max) NULL,

[sql_handle] [varbinary](64) NOT NULL,

[plan_generation_num] [bigint] NOT NULL,

[execution_count] [bigint] NOT NULL,

[dbid] [smallint] NULL,

[objectid] [int] NULL

) ON [PRIMARY]

insert into aaa_table select * from DMV_Top25_Recompile_Commands where 1=2

insert into aaa_table select * from DMV_Top25_Recompile_Commands where 1=2

insert into aaa_table select * from DMV_Top25_Recompile_Commands where 1=2

insert into aaa_table select * from DMV_Top25_Recompile_Commands where 1=2

insert into aaa_table select * from DMV_Top25_Recompile_Commands where 1=2

然后我们执行存储过程,收集profiler trace,同时继续监控性能监视器

开始重新执行存储过程aaa

这里我们可以看到sp recompilation/sec立刻变成了7

Profiler trace中可以看到每条insert语句上都触发了一个sp:recompile

脚本的查询结果:

可以看到plan_generation_num的值增加到6了。

为什么这样写存储过程会导致重编译?http://support.microsoft.com/kb/243586这篇文章中列举了多种会导致存储过程重编译的情况:

aaa这个存储过程符合这个条件:

The procedure interleaves Data Definition Language (DDL) and Data Manipulation Language (DML) operations.

因此我们的结论是,使用这个脚本去查询重编译次数高的存储过程是没有错的,但是这个脚本并不包含由于sp_recompile已经定义存储过程时使用了with recompile的选项而导致的存储过程重编译的情况。


#include <reg52.h> //包含51单片机相关的头文件 #include <intrins.h> #include <stdio.h> #include "light.h" #include "avoid.h" #include "track.h" #include "sport.h" //#include "model_change.h" #define uint unsigned int //重定义无符号整数类型 #define uchar unsigned char //重定义无符号字符类型 uchar code LedShowData[]={0x03,0x9F,0x25,0x0D,0x99, //定义数码管显示数据 0x49,0x41,0x1F,0x01,0x19};//0,1,2,3,4,5,6,7,8,9 uchar code RecvData[]={0x19,0x46,0x15,0x44,0x43,0x40,0x1C,0x18,0x08,0x0F}; //预设 前进 后退 左转 右转 uchar IRCOM[7]; static unsigned int LedFlash; //定义闪动频率计数变量 unsigned char RunFlag=0; //定义运行标志位 bit EnableLight=0; //定义指示灯使能位 /***********完成基本数据变量定义**************/ sbit S1State=P1^0; //D2寻光 右 //定义S1状态标志位 sbit S2State=P1^1; //D1寻光 左 //定义S2状态标志位 //sbit B1State=P1^2; //定义B1状态标志位 //sbit IRState=P1^3; //定义IR状态标志位 //sbit RunStopState=P1^4; //定义运行停止标志位 //sbit FontIRState=P1^5; //定义FontIR状态标志位 //sbit LeftIRState=P1^6; //定义LeftIR状态标志位 //sbit RightIRState=P1^7; //定义RightIRState状态标志位 /*************完成状态指示灯定义*************/ sbit S4=P3^2; //按键S4 sbit S3=P3^4; //按键S3 /*************完成按键端口的定义*************/ sbit LeftLed=P2^0; //D3 数码管指示灯 //sbit RightLed=P0^7; //定义前方右侧指示灯端口 /*************完成前方指示灯端口定义*********/ sbit LeftIR=P3^5; //D5 循迹 左 sbit RightIR=P3^6; //D6 循迹 右 sbit FontIR=P3^7; //避障 /*************完成红外探头端口定义***********/ sbit M1A=P0^0; //定义电机1正向端口 sbit M1B=P0^1; //定义电机1反向端口 sbit M2A=P0^2; //定义电机2正向端口 sbit M2B=P0^3; //定义电机2反向端口 /*************完成电机端口定义***************/ sbit B1=P0^4; // EVA 左电机使能 sbit RL1=P0^5; // EVB 右电机使能 sbit SB1=P0^6; //蜂鸣器 /*********蜂鸣器.端口定义**/ sbit IRIN=P3^3; //红外接收 /*********完成红外接收端口的定义*************/ #define ShowPort P2 //数码管端口定义P2 //extern void ControlCar(uchar CarType); //声明小车控制子程序 //void delayms(unsigned char x) //0.14mS延时程序 //{ // unsigned char i; //定义临时变量 // while(x--) //延时时间循环 // { // for (i = 0; i<13; i++) {} //14mS延时 // } //} //void Delay1ms(unsigned int i) //{ //unsigned char j,k; //do{ // j = 10; // do{ // k = 50; // do{ // _nop_(); // }while(--k); // }while(--j); //}while(--i); //} //void Delay() //定义延时子程序 //{ uint DelayTime=30000; //定义延时时间变量 // while(DelayTime--); //开始进行延时循环 // return; //子程序返回 //} void ControlCar(unsigned char ConType) //定义电机控制子程序 { tingzhi(); //小车静止状态 switch(ConType) //判断用户设定电机形式 { case 0: //模式切换按键100+ //判断用户是否选择形式2 currentMode = MODE_REMOTE; // 切回遥控模式 tingzhi(); // 停止电机 ShowPort = LedShowData[0]; // 显示0(遥控模式) break; case 1: //前进 //判断用户是否选择形式1 tingzhi(); //进入前进之前 先停止一段时间 防止电机反向电压冲击主板 导致系统复位 Delay1ms(240); LeftLed = 0 ; qianjin(); ShowPort=LedShowData[1]; //数码管显示数字2 break; case 2: //后退 //判断用户是否选择形式2 tingzhi(); //进入后退之前 先停止一段时间 防止电机反向电压冲击主板 导致系统复位 Delay1ms(240); LeftLed = 1 ; //LeftLed P20(数码管) houtui(); //M2电机反转 ShowPort=LedShowData[2]; //数码管显示数字1 break; case 3: //左转 //判断用户是否选择形式3 tingzhi(); //进入左转之前 先停止一段时间 防止电机反向电压冲击主板 导致系统复位 Delay1ms(240); zuozhuan(); //M2电机正转 ShowPort=LedShowData[3]; break; case 4: //右转 //判断用户是否选择形式4 tingzhi(); //进入右转之前 先停止一段时间 防止电机反向电压冲击主板 导致系统复位 Delay1ms(240); youzhuan(); //M1电机正转 //M2电机反转 ShowPort=LedShowData[4]; break; case 5: //停止 //判断用户是否选择形式8 currentMode = MODE_REMOTE; // 切回遥控模式 //tingzhi(); ShowPort=LedShowData[0]; break; //退出当前选择 case 6: //循迹 按键5 ShowPort=LedShowData[6]; // tingzhi(); //进入左转之前 先停止一段时间 防止电机反向电压冲击主板 导致系统复位 Delay1ms(240); // track(); //M2电机正转 currentMode = MODE_TRACK; // 循迹模式 break; //退出当前选择 case 7: //避障 按键2 ShowPort=LedShowData[7]; // tingzhi(); //进入左转之前 先停止一段时间 防止电机反向电压冲击主板 导致系统复位 Delay1ms(240); currentMode = MODE_AVOID ; // avoid(); //M2电机正转 break; //退出当前选择 case 8: //寻光 按键4 ShowPort=LedShowData[8]; tingzhi(); //进入寻光之前 先停止一段时间 防止电机反向电压冲击主板 导致系统复位 Delay1ms(240); //light(); currentMode = MODE_LIGHT; break; } } void IR_IN() interrupt 2 using 0 //定义INT2外部中断函数 { unsigned char j,k,N=0; //定义临时接收变量 EX1 = 0; //关闭外部中断,防止再有信号到达 delayms(15); //延时时间,进行红外消抖 if (IRIN==1) //判断红外信号是否消失 { EX1 =1; //外部中断开 return; //返回 } while (!IRIN) //等IR变为高电平,跳过9ms的前导低电平信号。 { delayms(1); //延时等待 } for (j=0;j<4;j++) // 采集4字节(j=0~3:地址码、地址反码、数据码、数据反码) 采集红外遥控器数据 { for (k=0;k<8;k++) //分次采集8位数据 { while (IRIN) //等 IR 变为低电平,跳过4.5ms的前导高电平信号。 { delayms(1); //延时等待 } while (!IRIN) //等 IR 变为高电平 { delayms(1); //延时等待 } while (IRIN) //计算IR高电平时长 { delayms(1); //延时等待 N++; //计数器加加 if (N>=30) //判断计数器累加值 { EX1=1; //打开外部中断功能 return; //返回 } } IRCOM[j]=IRCOM[j] >> 1; //进行数据位移操作并自动补零 if (N>=8) //判断数据长度 { IRCOM[j] = IRCOM[j] | 0x80; //数据最高位补1 } N=0; //清零位数计录器 } } if (IRCOM[2]!=~IRCOM[3]) //判断地址码是否相同 { EX1=1; //打开外部中断 return; //返回 } for(j=0;j<10;j++) //循环进行键码解析 { if(IRCOM[2]==RecvData[j]) //进行键位对应 { ControlCar(j); //数码管显示相应数码 } } EX1 = 1; //外部中断开 } void main(void) //主程序入口 { bit ExeFlag=0; LedFlash=3000; //对闪灯数据进行初始化 EX1=1; //同意开启外部中断1 IT1=1; //设定外部中断1为低边缘触发类型 EA=1; //总中断开启 ShowPort=LedShowData[0]; //数码管显示数字0 while(1) //程序主循环 { switch(currentMode) { case MODE_REMOTE: // 遥控模式:等待红外指令(由中断处理) break; case MODE_LIGHT: light(); // 执行寻光逻辑(函数内循环,直到模式改变) break; case MODE_TRACK: track(); // 执行循迹逻辑 break; case MODE_AVOID: avoid(); // 执行避障逻辑 break; } Delay(); //延时 RunStopState=~RunStopState; //运行指示灯状态转变 } } 根据以上代码,修改成可通过红外遥控器控制小车进行前进、后退、左转、右转、循迹、避障、寻光
最新发布
10-19
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值