笔记61-徐 编译与重编译 P372

笔记61-徐  编译与重编译 P372  

  1 --编译与重编译 P372 2012-12-28
  2 
  3 --当SQL收到任何一个指令,包括查询(query)、批处理(batch)、存储过程、触发器(trigger)
  4 --预编译指令(prepared statement)和动态SQL语句(dynamic SQL Statement)
  5 --要完成语法解释、语句解释,然后再进行“编译(compile)”,生成能够运行的“执行计划(execution plan)”
  6 --在编译的过程中,SQL会根据所涉及的对象的架构(schema)、统计信息以及指令的具体内容,估算
  7 --可能的执行计划,以及他们的成本(cost),最后选择一个SQL认为成本最低的执行计划
  8 
  9 --执行计划生成之后,SQL通常会把他们缓存在内存里,术语统称他们叫“plan cache”
 10 --以后同样的语句执行,SQL就可以使用同样的执行计划,而无须再做一次编译。这种
 11 --行为叫“重用(reuse)”但是有时候,哪怕是一模一样的语句,SQL下次执行还是要
 12 --再做一次编译。这种行为叫“重编译(recompile)”。执行计划的编译和重编译都是要
 13 --消耗资源的
 14 
 15 
 16 --执行计划的好坏当然决定了语句最终的执行速度。对于同样的一条语句,使用好的执行计划可能
 17 --会比差的要快几百倍,甚至上千倍。所以从这一个角度来讲,每运行一条语句,都把
 18 --他先编译一遍当然是最好的。他能够保证使用的执行计划是SQL能找到的最优的。但是SQL
 19 --每秒钟可能会运行成百上千的指令。如果每个都编译一遍,是资源的一种浪费。所以
 20 --SQL在这里也试图寻找一个平衡点,使用有限的compile/recompile,得到最好的整体性能
 21 
 22 
 23 --运行下面的指令,就能够看到SQL当前缓存的执行计划有哪些(请别在生产服务器上直接运行
 24 --因为上面往往有庞大的缓存)
 25 SELECT * FROM sys.[syscacheobjects]
 26 
 27 --对不同的指令调用方法,SQL做执行计划缓存和重用机制也有所不同。下面介绍最常见的几种
 28 --以及怎样通过跟踪sys.[syscacheobjects]视图和SQL Trace里的事件来分析是否有编译或者
 29 --执行计划重用
 30 
 31 
 32 --1、Adhoc语句
 33 --一组包含select,insert,update,delete的批处理指令。对这样的指令,只有前后完全一致
 34 --包括字母的大小写,空格,回车换行都一致,SQL才认为是两条一样的语句,才能够重用
 35 --执行计划。所以这个要求还是挺高的
 36 
 37 --例如,运行下面的指令,连续两次查询sys.[syscacheobjects]视图
 38 DBCC freeproccache
 39 GO
 40 SELECT * FROM sys.[syscacheobjects]
 41 go
 42 SELECT * FROM [sys].[syscacheobjects]
 43 go
 44 
 45 
 46 --在第二个结果集里会看到两个adhoc的执行计划。他们的差别就在于一个有回车
 47 --一个没有回车
 48 
 49 
 50 --但是如果语句是一样的,那么第二句话就可能会重用前面的执行计划。所以adhoc
 51 --语句并不一定没有执行计划的重用
 52 
 53 --例如下面,把同样的一句话跑两遍,会发现只有一个执行计划,而且他被使用过两次
 54 --(字段usecount=2)
 55 
 56 DBCC freeproccache
 57 GO
 58 SELECT  * FROM sys.[syscacheobjects]
 59 go
 60 SELECT * FROM sys.[syscacheobjects]
 61 go
 62 
 63 
 64 --在SQL Trace里,也能看到第一次执行有过编译事件(SP:CacheInsert),第二次执行就
 65 --重用了先前的执行计划(SP:CacheHit)
 66 
 67 
 68 --2、用EXEC()的方式运行动态SQL语句
 69 --有些应用程序为了开发上的灵活度,在程序运行过程中,动态地拼接一个语句字符串,然后
 70 --用exec()的方式执行。这种调用方法被称为“动态SQL dynamic SQL”。他的好处是灵活
 71 --可以根据客户的选择,动态生成指令,而不仅限于预先定义的那几种。但是他的缺点也是
 72 --太灵活,客户发过来的语句每次都不一样,或者语句主体部分是一样的,但是参数不一样
 73 --SQL都要做编译。这点和adhoc语句是一样的
 74 
 75 
 76 --例如下面这个例子,同一句话运行了三遍。第一遍和第二遍用的是一样的参数值,所以第二次
 77 --没有再做编译(有事件SP:CacheHit)。但是第三次的参数值不同,他就再编译了一遍
 78 --(SP:CacheMiss和SP:CacheInsert)
 79 
 80 
 81 DBCC freeproccache
 82 GO
 83 DECLARE @dbid VARCHAR(10)
 84 SET @dbid='9'
 85 EXEC('select * from sys.syscacheobjects where dbid<='+@dbid)
 86 SET @dbid='9'
 87 EXEC('select * from sys.syscacheobjects where dbid<='+@dbid)
 88 SET @dbid='6'
 89 EXEC('select * from sys.syscacheobjects where dbid<='+@dbid)
 90 GO
 91 
 92 
 93 
 94 --第二次运行和第三次运行SQL Trace里的事件不同
 95 
 96 --在应用里经常会有同样的语句被不同的客户以不同的参数调用到。如果一定要参数一样才能重用
 97 --执行计划,那执行计划重用的机会就很低了。所以adhoc语句和dynamic SQL不是最佳的调用
 98 --方法。在SQL里,还有其他一些方法,可以对结构相同但是参数值不同的指令做执行计划重用
 99 
100 
101 --3、自动参数化的查询(auto-parameterized query)
102 --对于一些比较简单的查询,SQL2005自己就可以做自动参数化,把语句里的参数用一个变量代替
103 --以提高执行计划的可重用性。
104 --例如,运行下面这几条语句:
105 
106 USE [AdventureWorks]
107 GO
108 DBCC freeproccache
109 go
110 SELECT [ProductID],[SalesOrderID]
111 FROM sales.[SalesOrderDetail]
112 WHERE [ProductID]>1000
113 GO
114 
115 SELECT [ProductID],[SalesOrderID]
116 FROM sales.[SalesOrderDetail]
117 WHERE [ProductID]>2000
118 GO
119 
120 SELECT * FROM sys.[syscacheobjects]
121 go
122 
123 
124 --两条查询语句一模一样,只是参数的值不同。当察看缓存里的执行计划时,就能发现
125 --SQL不但缓存了两条语句自己的执行计划,还缓存了一个参数化了的执行计划
126 
127 --如果再运行一句类似的语句:
128 SELECT [ProductID],[SalesOrderID]
129 FROM sales.[SalesOrderDetail]
130 WHERE [ProductID]>3000
131 GO
132 
133 
134 --在SQL Trace里,可以看到这句话在开始之前,先有一个SP:CacheHit的事件。语句在缓存
135 --中找到了一个参数化后的执行计划。然后他根据这个执行计划,做出了自己的adhoc执行计划
136 --(SP:CacheInsert)。这也能节省一部分编译时间
137 
138 
139 
140 --4、用sp_executesql的方式调用的指令
141 --查询自动参数化在很多种条件下是不支持的,而且他还是要为每句查询生成一个adhoc的执行计划
142 --所以他并不是减少编译的最优手段。改用sp_executesql能够更有效地增加执行计划重用。用
143 --下面这个例子,两个查询结构一样,仅参数不同
144 
145 USE [AdventureWorks]
146 GO
147 DBCC freeproccache
148 GO
149 EXEC [sys].[sp_executesql] N'SELECT p.[ProductID],p.[Name],p.[ProductNumber] FROM [Production].[Product] p
150 INNER JOIN [Production].[ProductDescription] pd
151 ON p.[ProductID]=pd.[ProductDescriptionID]
152 WHERE p.[ProductID]=@a' ,N'@a int' ,170
153 GO
154 
155 
156 EXEC [sys].[sp_executesql] N'SELECT p.[ProductID],p.[Name],p.[ProductNumber] FROM [Production].[Product] p
157 INNER JOIN [Production].[ProductDescription] pd
158 ON p.[ProductID]=pd.[ProductDescriptionID]
159 WHERE p.[ProductID]=@a' ,N'@a int' ,1201
160 GO
161 
162 SELECT * FROM sys.[syscacheobjects]
163 GO
164 
165 --在执行计划缓存里,只会看到一份执行计划(prepared),不再有adhoc的那一份
166 
167 --用SQL Trace,可以看到第一次执行时有SP:CacheInsert事件,第二次就只有SP:CacheHit事件了
168 --说明第二次语句执行已经完全不需要做编译。从而提高了执行计划重用性
169 
170 
171 --5、存储过程(stored procedure)
172 --对用户经常要调用的指令,把他们做成存储过程,既方便管理,规范脚本,又能够大大提高
173 --执行计划重用率,是值得推荐的一种做法。从SQL的角度,最好绝大多数指令都能够以存储
174 --过程的方式调用,尽量少使用Dynamic SQL的方式
175 
176 --例如把前面的那个查询做成一个存储过程,然后用EXEC加参数的方式调用。可以清楚地看见
177 --第二次调用重用了第一次执行计划生成的执行计划
178 
179 
180 
181 
182 
183 
184 -------------------------重编译----------------------------------------------------------
185 --但是有些时候,SQL为了确保返回正确的值,或者有性能上的顾虑,有意不重用缓存在内存里
186 --的执行计划,而现场编译一份。这种行为,被称为重编译(recompile)。下面是比较常见
187 --的会发生重编译的情形:
188 
189 
190 --1、当指令或者批处理所涉及的任何一个对象(表格或者视图)发生了架构(schema)变化
191 --例如,在表或者视图上添加或删除了一个字段,添加或者删除了一个索引,在表上添加或者
192 --删除了一个约束条件(constraints)等。定义发生了变化,原来的执行计划就不一定正确
193 --当然要重编译
194 
195 
196 
197 --2、运行过sp_recompile
198 --当用户在某个存储过程或者触发器上运行过sp_recompile后,下一次运行他们就会发生
199 --一次重编译。如果用户在某个表或者视图上运行了sp_recompile,那么所有引用到
200 --这张表(或者视图)的存储过程在下一次运行前,都要做重编译
201 
202 
203 
204 --3、有些动作会清除内存里的所有执行计划,迫使大家都要做重编译
205 --例如,下列动作会清除整个SQL服务器缓存的所有执行计划:
206 --Detach一个数据库
207 --对数据库做了升级,在新的服务器上,会发生执行计划清空
208 --运行了DBCC freeproccache
209 --运行了reconfigure语句
210 --运行了alter database..collate语句修改了某个数据库的字符集(collation)
211 
212 
213 --下列动作会清除SQL服务器缓存的某个数据库的执行计划:
214 --DBCC FLUSHPROCINDB
215 --* DBCC FLUSHPROCINDB (db_id)
216 --清除SQL Server 2000服务器内存中的某个数据库的存储过程缓存内容
217 DECLARE @a INT
218 SELECT @a=DB_ID('gposdb')
219 DBCC flushprocindb(@a)
220 --ALTER DATABASE ...MODIFY NAME语句
221 --ALTER DATABASE ...SET ONLINE语句
222 --ALTER DATABASE...SET OFFLINE语句
223 --ALTER DATABASE...SET EMERGENCY语句
224 --DROP DATABASE 语句
225 --当一个数据库自动关闭时
226 --DBCC CHECKDB语句结束时
227 
228 
229 
230 --4、当下面这些SET 开关值变化后,先前的那些执行计划都不能重用
231 --ansi_null_dflt_off,ansi_null_dflt_on,ansi_nulls,_ansi_padding
232 --ansi_warnings,arithabort,concat_null_yields_null,datefirst,dateformat,
233 --forceplan,language,no_browsetable,numeric_roundabort,quoted_identifier
234 
235 --这是因为这些SET开关会影响语句的执行的行为,甚至带来不同的结果。他们发生变化了
236 --SQL就要根据新的设置重做执行计划
237 
238 
239 --5、当表格或者视图上的统计信息发生变化后
240 --当统计信息被手动更新后,或者SQL发现某个统计信息需要自动更新时,SQL会对所涉及
241 --的语句都做重编译
242 
243 
244 
245 
246 --需要说明的是,在SQL里,执行计划重用并不一定是一件好事,而编译/重编译也不一定
247 --是一件坏事。计划重用可以帮助SQL节省编译时间,对降低CPU使用率和减少阻塞都有好处
248 --但是缺点是每次重用的计划并不一定是最合适的计划。后面的参数嗅探parameter sniffing
249 --就是典型的计划重用带来的负效应。编译和重编译当然能给当前运行的语句带来尽可能准确
250 --执行计划,但是对于经常运行的语句,尤其是一些执行速度比较快的语句,可能其编译时间
251 --占最后总时间的相当大比例。这对资源来讲是一个很大的浪费
252 
253 
254 --一般来说,SQL能够很好地在编译与重编译之间做平衡,大部分情况下没什么问题。如果发现
255 --系统因为编译和重编译太多,或者重用了不准确的计划,导致了性能问题,那就要根据具体
256 --情况做调整。在SQL里,能对计划重用和编译/重编译产生影响的功能主要有:
257 
258 
259 --1、使用存储过程,或者[sys].[sp_executesql]的方式调用会被重复使用的语句,而不要直接
260 --用adhoc语句或者dynamic SQL
261 
262 --2、在语句里引用对象(表,视图,存储过程等),要带上他的schema名字(full qualified object name)
263 --而不光是对象自己的名字
264 
265 --例如:对于一个对象名,[SalesOrderDetail],dbo调用他,调用到的是[dbo].[SalesOrderDetail]
266 --而用户Sales调用他,调用到的可能是[Sales].[SalesOrderDetail]。所以对于同一句语句,SQL
267 --可能会更倾向于保存多份执行计划。使用对象全名,能够更好地“引导”SQL重用执行计划
268 
269 --3、将数据库parameterization属性设置为forced
270 --SQL2005以后,可以在单个数据库上开启强制参数化。也就是说,对于在这个数据库下运行的
271 --大部分语句,SQL都会先参数化,再运行。如果应用经常用adhoc方式调用一样的语句,强制
272 --参数化可能会有所帮助
273 
274 --4、统计信息更新
275 --统计信息手工或者自动更新后,对和他有关的执行计划都不再能重用,而会产生重编译
276 --这样能够保证下次运行的语句能够根据数据变化使用新的执行计划
277 
278 --5、create procedure...with recompile选项和exec ....with recompile选项
279 --在创建或者调用存储过程的时候使用“with recompile”,会强制SQL在调用这个存储过程
280 --的时候,永远都先编译,再运行。哪怕有合适的缓存执行计划,也不会重用
281 
282 --这个选项一般用在DBA已经确定了对某个特定的存储过程,或者是特定语句执行计划重用
283 --会带来负面影响时(例如parameter sniffing后果比较严重)
284 
285 
286 --6、用户调用了sp_recompile
287 USE [pratice]
288 GO
289 EXEC [sys].[sp_recompile] @objname = N'dbo.bulkinserttest' -- nvarchar(776)
290 
291 --sp_recompile也会产生相关对象的重编译。当DBA怀疑某个对象上现在缓存的计划
292 --不合适,已经造成性能问题时,可以运行sp_recompile,让SQL重新编译
293 
294 --7、用户在调用语句的时候,使用了“keep plan”或者“keepfixed plan”这样的查询提示(Hint)
295 --keep plan放宽了对临时表的重编译阀值,使得SQL像对普通表一样对待临时表,不会在上面
296 --做额外的重编译。用户可以像下面这样使用他:
297 SELECT b.col4,SUM(a.col1)
298 FROM [dbo].permtable a INNER JOIN #temptable b ON a.col1=b.col2
299 WHERE b.col3<100
300 GROUP BY b.col4
301 OPTION(KEEP PLAN)
302 
303 
304 --keepfixed plan强制查询优化器不因统计信息的更改而重新编译查询。只有在基础表的结构
305 --发生变化后,或者有人运行过sp_recompile以后,才会发生重编译。用户可以像下面这样
306 --使用他
307 USE [AdventureWorks]
308 GO
309 SELECT c.[TerritoryID],COUNT(*) AS number,c.salespersonid
310 FROM sales.[Store] s INNER JOIN sales.[Customer] c
311 ON s.[CustomerID]=c.[CustomerID]
312 WHERE s.[Name] LIKE '%Bike%' AND [c].salespersonid>285
313 GROUP BY c.[TerritoryID],c.salespersonid
314 ORDER BY number DESC
315 OPTION(KEEPFIXED PLAN)
316 
317 
318 
319 --总之,DBA可以根据实际情况,选择某一种或某几种方法来影响SQL的编译/重编译行为
320 --在SQL里,也提供了很多跟踪和分析编译与执行计划重用行为的方法。有些前面已经使用
321 --过了。现在在这里再总结一下
322 
323 
324 --1、系统管理视图sys.syscacheobjects
325 --从sys.syscacheobjects里可以看到SQL缓存的所有执行计划,以及他们的相关信息
326 
327 --一些比较常用的字段有:
328 --cacheobjtype:缓存中的对象类型
329 --compiled plan:编译计划,也就是我们通常讲的执行计划
330 --executable plan:可执行计划
331 --parse tree:分析树
332 --cursor:游标
333 --extended stored procedure:扩展存储过程
334 
335 --objtype:对象的类型
336 --可以缓存执行计划的对象很多,包括有:
337 
338 --proc:存储过程
339 --prepared:预定义语句
340 --adhoc:即席查询(由sqlcmd或osql实用工具而不是远程过程调用作为语言事件提交的TSQL)
341 --repproc:复制筛选过程
342 --trigger:触发器
343 --view:视图
344 --default:默认值
345 --usrtab:用户表
346 --systab:系统表
347 --check:check约束
348 --rule:规则
349 --setopts:影响编译计划的SET选项设置
350 --refcounts:引用该缓存对象的其他缓存对象数,计数1为基数。每被重新引用一次,加1
351 --usecounts:自开始以来使用该缓存对象的次数。refcounts和usecounts这两个值越大,
352 --说明缓存重用得越多,这个缓存越有用
353 
354 --pagesused:缓存对象占用的页数。也就是缓存消耗的内存数目
355 
356 --唯一再次强调的是,在一个生产环境里,SQL缓存的执行计划可能会很多。要去查询这张表
357 --最好带上一些过滤条件,或者加上top 1000之类的子句,控制返回集的大小
358 
359 
360 --2、清除执行计划缓存的语句
361 --如果DBA有充分的理由怀疑当前的问题和内存里缓存的执行计划有关,可以用下面两句话之一
362 --清除执行计划,顺便也释放内存(所以在和内存相关的问题上有时也有用)
363 DBCC freeproccache
364 DECLARE @a INT
365 SELECT @a=DB_ID('gposdb')
366 DBCC flushprocindb(@a)
367 
368 --3、SQL Trace里的一些和编译有关的事件
369 --在SQLTrace里有一些事件可用很有效地跟踪编译和重编译行为
370 --SQLTrace里的一些和编译有关的事件
371 --cursors -cursorrecompile:当游标所基于的对象发生架构变化,导致的TSQL游标做的重编译
372 --performance -auto stats:发生自动创建或者更新统计信息的事件
373 
374 --stored procedures 下面有好几个很有用的事件:
375 --SP:CacheHit:说明当前语句在缓存里找到了一个可用的执行计划
376 --SP:CacheInsert:当前有一个新执行计划被插入到缓存里
377 --SP:CacheMiss:说明当前语句在缓存里找不到一个可用的执行计划
378 --SP:CacheRemove:有执行计划被从缓存里移除。内存有压力的时候会发生这样的行为
379 --SP:Recompile:一个存储过程发生了重编译。这个事件有一个数据字段叫EventSubClass
380 --记录了重编译发生的原因。如果DBA要跟踪为什麽一条指令老是重编译,跟踪这个事件
381 --的这个字段很有用,他一共有11种可能的原因。所以在处理重编译问题时,这个事件
382 --很有帮助
383 
384 
385 --1=架构已更改
386 --2=统计已更改
387 --3=重新编译DNR
388 --4=所设置的选项已更改
389 --5=临时表已更改
390 --6=远程行集已更改
391 --7=浏览Perm的方式已更改
392 --8=查询通知环境已更改
393 --9=MPI视图已更改
394 --10=光标选项已更改
395 --11=使用重编译选项
396 
397 
398 --而SQL2000中,只要批处理中的任何一句语句导致重新编译,就会重新编译整个批处理,
399 --无论此批处理是通过存储过程、触发器、即席批查询,还是通过预定义的语句进行提交
400 --在SQL2005和更高版本中,只会重新编译批处理中导致重新编译的语句。从这一点来看
401 --SQL2005比SQL2000更节约一点。因为这种变化,在SQL2005以后,要改用“TSQL-SQL:StmtRecompile”
402 --这个事件来跟踪重编译,因为现在的重编译都发生在语句一级
403 
404 
405 --4、一些性能监视器计数器
406 --相关计数器
407 --性能对象                                   计数器
408 --SQLSERVER:BUFFER MANAGER:    buffer cache hit ratio,lazy writes/sec ,procedure cache pages,total pages
409 --SQLSERVER:Cache Manager:    cache hit ratio,cache object counts,cache pages ,cache use counts/sec
410 --SQLSERVER:MEMORY MANAGER:    sql cache memory(kb)
411 --SQLSERVER:SQL STATISTICS:    auto-param attmpts/sec,batch request/sec,failed auto-params/sec,safe auto
412 --                             -param/sec, sql compilations/sec,sql re-compilations/sec,unsafe auto-params/sec

 

posted @ 2013-07-29 09:01  桦仔  阅读(593)  评论(0编辑  收藏  举报