SQLServer中的执行计划缓存由于长时间缓存对性能造成的干扰
先抛出一个性能问题,前几天遇到一个生产环境性能极其低下的存储过程,开发人员根据具体的业务逻辑和返回的数据量,猜测到这个存储过程的执行应该不会有这么慢。 当时意识到可能是执行计划缓存的问题,因为当前这个存储过程的写法还是比较遵守参数化SQL的规范的(如果是动态即席查询SQL就不会有问题了) ? 特定语句的执行计划缓存信息 参考如下sql,查询出来某些指定语句的执行计划缓存信息。 SELECT st.Text,? ? ? ?SUBSTRING( ? ? ? ? ? ?st.Text,(qs.statement_start_offset/2)+1,? ? ? ? ? ?((CASE qs.statement_end_offset ? ? ? ? ? ?WHEN -1 THEN DATALENGTH(st.Text) ? ? ? ? ? ?ELSE qs.statement_end_offset ? ? ? ? ? ?END - qs.statement_start_offset)1 ? ? ? ? ? ?),? ? ? ?qp.query_plan,? ? ? ?qs.plan_handle,? ? ? ?qs.sql_handle,255); line-height: 1.5 !important;">DB_NAME(st.dbid) as dbname,? ? ? ?qs.creation_time,? ? ? ?qs.last_execution_time,255); line-height: 1.5 !important;">getdate() as currenttime,? ? ? ?qs.execution_count,? ? ? ?qs.last_worker_time,? ? ? ?qs.last_physical_reads,? ? ? ?qs.last_logical_reads,? ? ? ?qs.last_elapsed_timeFROM sys.dm_exec_query_stats qs ? ?CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) as st ? ?OUTER APPLY sys.dm_exec_query_plan(qs.plan_handle) as qp WHERE 1=1 and st.text like '%模糊匹配存储过程中具体标记性的sql文本%'order by creation_time desc 对于类似如下的查询结果,从查询到的结果中可以看出,直接打开query_plan的xml,可以看到这个存储过程中的语句的执行计划情况,当前我这里仅仅是一个示例 ? ?对于当前这个缓存的执行计划的编译参数,可以将query_plan的xml信息复制出来,用notepad++之类的文本编辑器格式化之后显示,查询器编译时候的参数, ? 对于一开始提到的问题,结果就是当前执行的存储过程中的语句,其执行计划已经被缓存超过了一天,因为尚未带到触发“重编译”的条件,该缓存继续保留在内存中。 个人猜测有两个原因,不过也不完全确定, 更何况如果缓存了某一个较少概率出现的参数,或者类似于产生parameter sniff的参数问题(当然parameter sniff问题就另当别论),影响甚至就更大。 如下截图是某生产环境服务器上的,可以看到,在业务存储过程或者SQL语句的执行计划缓存,多的可以缓存了超过20天,另外还有缓存的超过了3天,4天的。 ? ? 据观察,SQL Server在对待执行计划缓存的内存占用,是轻易不会去清理的,可以认为这部分内存(相对于数据缓存)的要求优先级是比较高的, 执行计划缓存的基础知识 1,什么是执行计划缓存 SQL Server查询引擎接收到sql语句之后,首先会对其进行语法,语义的解析,然后进行编译,之后生成执行计划,对于可满足缓存的要求的sql语句,SQLServer会对其进行缓存。 2,执行计划缓存的作用 减少SQL的编译,SQLServer接收到存在执行计划缓存的SQL语句的时候,可以直接使用缓存的执行计划进行执行,避免重新编译造成的资源上和时间上的消耗。 3,当前执行计划缓存占用的空间 参考如下SQL,可以使用sys.dm_os_memory_clerks或者sys.dm_exec_cached_plans ?来查询执行计划缓存已经占用的空间。 --CACHESTORE_SQLCP,动态sql,预编译sql执行计划select * from sys.dm_os_memory_clerkswhere type = CACHESTORE_SQLCPproc:存储过程prepared:预定义语句--Adhoc:动态查询select sum(size_in_kb)1024.01024.0 as size_in_GB FROM( ? ?SELECT ?objtype,? ? ? ? ? ? ?size_in_bytesAS ?size_in_kb ? ?FROM ? ?sys.dm_exec_cached_plans ? ? ?WHERE objtype IN ( Proc',PreparedAdhoc') )t 如下是某服务器上的执行计划缓存信息,两者查询出来的结果基本上一致。 4,执行计划缓存可最大占用的空间 执行计划占用的内存空间是一种称之为Stolen Memory的内存类型,Stolen Memory包括执行计划缓存,以及Session实时计算所需要的内存,比如排序,聚合计算,hash join等等。 1)不超过8GB的情况下:Stolen Memory =?Target Mermory * 75% ? 可见,随着最大可用内存的增加,Stolen Memory并不是线性递增的,而是增加程度趋于减少(增加程度的导数是减少的) 需要注意的是,执行计划缓存可最大占用的空间仅仅是Stolen Memory的一部分,并不是Stolen Memory的全部。 ? ? 那么问题就来了,这里可以认为,执行计划缓存可最大占用的空间是无法直接控制的, 5,关于即席查询(ad?hoc)SQL的配置 这一点根本文关系不大,简单提一下,对于ad?hoc,也即动态sql,因为其参数很有可能每次都发生变化,类似SQL就没有必要缓存了, 开启 optimize for ad hoc workloadssp_CONFIGURE show advanced options1reconfiguregosp_CONFIGURE optimize for ad hoc workloadsgo 这一点配置的意义何在?内存对数据库来说是很重要的资源,如果Stolen Memory各项内存使用都可以配置,在尽可能满足Stolen Memory的基础上,更多更久地缓存数据才是王道。 如果解决潜在可能过期的执行计划 上述分析说明,在Stolen Memory尚未感受到内存压力,或者是执行计划清理机制尚未清理老化的执行计划缓存的时候,执行计划缓存是依旧保存在内存中的。 Remove the specific plan from the cache.清楚特定的执行计划DBCC FREEPROCCACHE (0x060006001ECA270EC0215D05000000000000000000000000);GO 对于DBCC FREEPROCCACHE去清理执行特定计划缓存,此种方式仅可以用来应急使用。 更多的时候,已经缓存过一段时间的执行计划,哪些是本应该被清理的,那些是可以继续缓存使用的?这一点很难主观地来界定。 ? 当然,这里是不包括“采用with recompile语句存储过程的重编译或者option(recompile)基于语句的重编”来避免执行计划缓存的。 大概想了一下,使用类似如下SQL,采用动态执行DBCC FREEPROCCACHE的方式,可以达到预期的目的。 if object_id(tempdb..#t_remove_expired_plan') is not null drop table #t_remove_expired_planGOcreate table #t_remove_expired_plan ( ? ?id int identity(1,0); font-weight: bold; line-height: 1.5 !important;">1),? ?plan_handle varbinary(500) )insert into #t_remove_expired_plan (plan_handle) select ?qs.plan_handlefrom sys.dm_exec_query_stats qs where creation_time< dateadd(hh,0); font-weight: bold; line-height: 1.5 !important;">24,255); line-height: 1.5 !important;">getdate())declare @exists_data bit = @v_plan_handle varbinary(500)@str_sql varchar(1000)while @exists_data begin select top @v_plan_handle = plan_handle from #t_remove_expired_plan ? ?if(null) ? ?begin execute sp_executesql NDBCC FREEPROCCACHE(@plan_handle)@plan_handle varbinary(500)@plan_handle = @v_plan_handle end delete top (1) if exists(from #t_remove_expired_plan) ? ?set 1 else 0 endend 总结: 执行计划缓存原本是为了减少编译SQL带来的资源以及时间上的消耗,在可存在可重用的执行计划缓存的情况下提高SQL的执行效率 微软的平台,一直以来本着简单易容,快速上手的特点,对用户做了大量的封装(屏蔽),在个性化的可配置化上,与其他数据库尤其是开源产品相比显得有些不足。 本文出处:http://www.cnblogs.com/wy123/p/7190785.html? (保留出处并非什么原创作品权利,本人拙作还远远达不到,仅仅是为了链接到原文,因为后续对可能存在的一些错误进行修正或补充,无他) 图书推荐: 简介:《高效程序员的45个习惯:敏捷开发修炼之道(修订版)》总结并生动地阐述了成为高效的开发人员所需具备的45个习惯、思想观念和方法,涵盖了软件开发进程、编程和调试工作、开发者态度、项目和团队管理以及持续学习等几方面。 购买方式: |