你的位置:首页 > 数据库

[数据库]参数化(一):计划缓存


很多时候,当我执行查询调优的时候,引发查询性能糟糕的问题一般都是与参数化相关的。

一方面,参数化是查询处理器核心的基本主题。它能显著影响查询性能。另一方面,大家很少对这一主题进行详尽的了解。

因此我准备写一个系列的随笔来介绍关于参数化的问题。第一篇我将介绍关于计划缓存的内容。为了理解参数化,有必要先理解理解执行计划如何被缓存。

     SQLServer保留一定数量的内存来保存执行计划缓存。这就是执行计划(和一下其他结构)被缓存为了未来重用的地方。查询(或语句)和批处理之间的区别时会引发混淆。前者是作为一个原子一次执行一个查询的单一命令。后者是一个被解析、简化、优化、编译并最终被执行的单位,包含多个语句。这个单位为优化器生成一个执行计划,因此执行计划缓存存储执行计划,每一个代表一个批处理。

     使用sys.dm_exec_cached_plansDMV可以查看执行计划缓存的内容。如果你打算看批处理文档和计划

SELECT	BatchText			= BatchTexts.text ,	QueryPlan			= BatchPlans.query_plan ,	ExecutionCount		= CachedPlans.usecounts ,	ObjectType			= CachedPlans.objtype ,	Size_KB				= CachedPlans.size_in_bytes / 1024FROM	sys.dm_exec_cached_plans AS CachedPlansCROSS APPLY	sys.dm_exec_query_plan (plan_handle) AS BatchPlansCROSS APPLY	sys.dm_exec_sql_text (plan_handle) AS BatchTexts;

 

    当查询处理器遇到一个批处理时,首先检查是否在缓存中已经有能被重用的计划。这是因为分析和编译批处理的成本是相当昂贵的。如果已经有了这个精确的执行计划,那么能节省大量的资源和时间。查询处理器如何查询批处理是否在缓存中的那?当一个新的批处理产生,它的哈希值被计算并且与已经在缓存中的执行计划比较。假如匹配的到,批处理文档按顺序去验证这的确是相同的批处理(这是因为多重文档值可能结果是相同的哈希值)。

     一些其他元素也需要被比较,例如ANSI_NULLS 和ARITHABORT等选项。有几个类似的设置选项会影响当前会话中的查询如何被执行。两个会话执行完全相同的批处理,使用不同的设置选项可能会产生不同的结果并且这就是为什么必须去通过不同的油画过程和产生不同的执行计划。因此当查询处理器发现一个计划在缓存中,它需要比较这7个设置选项是否相同。

     如果没有匹配上,则需要经过解析、简化、油画、和编译计划等过程。新产生的计划将被放到内存中为将来使用(多数情况)。

    一旦查询处理器发现缓存中有一个执行计划,它仍然会验证执行计划是否仍然可用。例如,潜在的表和索引的架构已经在计划生成后发生了变化,或许新的列被加入表中,或者索引被删除…;另外一个原因统计信息过期。如果计划基于统计信息生成的,就会被标记过期(表的大量变动)。这些都会导致新的执行计划无效。

    如果计划是有效的,查询处理器最终能执行它。整个过程看起来很长,但是实际上是非常快。如果计划无效,那么就会触发重现编译事件。这意味着再次进行批处理优化,然后新的执行计划被生成、放置在原来无效的缓存内存中。重编译已经存在的计划比编译新的要快。因为它没必有解析和简化步骤。

 

这里有一个图来总结这一过程

Plan Caching

 

    一般来说,你的目标是提高计划重用率。越是重用的多意味在编译相同执行计划时越少的资源被浪费,批处理更高效、性能更好。

    现在请看下面这个类似的执行查询应用:

SELECT	OrderId ,	OrderDate ,	OrderAmount ,	OrderStatusFROM	Sales.OrdersWHERE	CustomerId = 73922;

 

    如果查询一秒内不执行50次,每次有不同顾客ID,那么这个计划缓存将很快爆炸。每个查询用不同的用户将被当做新的批处理(因为查询的哈希值在缓存中找不到),并且将必须经历整个解析--优化的处理过程。除此之外,每一个计划一定要放到内存中,因此大量内存分配活动在幕后进行。既然内存有限,有大量的计划存储。计划缓存将增加大量数据缓存,因此更少的数据也存储在缓存中,并且内存管理器将必须移除旧的计划缓存以便去有更多的空间为新的计划。

     这就是参数化扮演重要的角色。下一章将介绍执行查询的七种方式。