参数化(一):计划缓存
简介
很多时候,当我执行查询调优的时候,引发查询性能糟糕的问题一般都是与参数化相关的。一方面,参数化是查询处理器核心的基本主题。它能显著影响查询性能。另一方面,大家很少对这一主题进行详尽的了解。
因此我准备写一个系列的随笔来介绍关于参数化的问题。第一篇我将介绍关于计划缓存的内容。为了理解参数化,有必要先理解理解执行计划如何被缓存。
SQLServer保留一定数量的内存来保存执行计划缓存。这就是执行计划(和一下其他结构)被缓存为了未来重用的地方。查询(或语句)和批处理之间的区别时会引发混淆。前者是作为一个原子一次执行一个查询的单一命令。后者是一个被解析、简化、优化、编译并最终被执行的单位,包含多个语句。这个单位为优化器生成一个执行计划,因此执行计划缓存存储执行计划,每一个代表一个批处理。
使用sys.dm_exec_cached_plansDMV可以查看执行计划缓存的内容。如果你打算看批处理文档和计划XML,那么可以使用下面的查询:
SELECT
BatchText = BatchTexts.text ,
QueryPlan = BatchPlans.query_plan ,
ExecutionCount = CachedPlans.usecounts ,
ObjectType = CachedPlans.objtype ,
Size_KB = CachedPlans.size_in_bytes / 1024
FROM
sys.dm_exec_cached_plans AS CachedPlans
CROSS APPLY
sys.dm_exec_query_plan (plan_handle) AS BatchPlans
CROSS APPLY
sys.dm_exec_sql_text (plan_handle) AS BatchTexts;
当查询处理器遇到一个批处理时,首先检查是否在缓存中已经有能被重用的计划。这是因为分析和编译批处理的成本是相当昂贵的。如果已经有了这个精确的执行计划,那么能节省大量的资源和时间。查询处理器如何查询批处理是否在缓存中的那?当一个新的批处理产生,它的哈希值被计算并且与已经在缓存中的执行计划比较。假如匹配的到,批处理文档按顺序去验证这的确是相同的批处理(这是因为多重文档值可能结果是相同的哈希值)。
一些其他元素也需要被比较,例如ANSI_NULLS 和ARITHABORT等选项。有几个类似的设置选项会影响当前会话中的查询如何被执行。两个会话执行完全相同的批处理,使用不同的设置选项可能会产生不同的结果并且这就是为什么必须去通过不同的油画过程和产生不同的执行计划。因此当查询处理器发现一个计划在缓存中,它需要比较这7个设置选项是否相同。
如果没有匹配上,则需要经过解析、简化、油画、和编译计划等过程。新产生的计划将被放到内存中为将来使用(多数情况)。
一旦查询处理器发现缓存中有一个执行计划,它仍然会验证执行计划是否仍然可用。例如,潜在的表和索引的架构已经在计划生成后发生了变化,或许新的列被加入表中,或者索引被删除…;另外一个原因统计信息过期。如果计划基于统计信息生成的,就会被标记过期(表的大量变动)。这些都会导致新的执行计划无效。
如果计划是有效的,查询处理器最终能执行它。整个过程看起来很长,但是实际上是非常快。如果计划无效,那么就会触发重现编译事件。这意味着再次进行批处理优化,然后新的执行计划被生成、放置在原来无效的缓存内存中。重编译已经存在的计划比编译新的要快。因为它没必有解析和简化步骤。
一般来说,你的目标是提高计划重用率。越是重用的多意味在编译相同执行计划时越少的资源被浪费,批处理更高效、性能更好。
现在请看下面这个类似的执行查询应用:
SELECT
OrderId ,
OrderDate ,
OrderAmount ,
OrderStatus
FROM
Sales.Orders
WHERE
CustomerId = 73922;
如果查询一秒内不执行50次,每次有不同顾客ID,那么这个计划缓存将很快爆炸。每个查询用不同的用户将被当做新的批处理(因为查询的哈希值在缓存中找不到),并且将必须经历整个解析--优化的处理过程。除此之外,每一个计划一定要放到内存中,因此大量内存分配活动在幕后进行。既然内存有限,有大量的计划存储。计划缓存将增加大量数据缓存,因此更少的数据也存储在缓存中,并且内存管理器将必须移除旧的计划缓存以便去有更多的空间为新的计划。
这就是参数化扮演重要的角色。下一章将介绍执行查询的七种方式。
- Linux系统yum命令安装软件时保留(下载)rpm包
- Go语言读写数据库
- 《Android 创建线程源码与OOM分析》
- 微信 Android 视频编码爬过的那些坑
- 少年,这有套《街霸2》AI速成心法,想传授于你……
- 你知道android的MessageQueue.IdleHandler吗?
- 《Android基础:Fragment,看这篇就够了》
- Android 7.0中ContentProvider实现原理
- 《iOS APP 性能检测》
- iOS 11 安全区域适配总结
- Linux下巧用chattr、watch命令的实例
- 【特斯拉组件】iOS高性能PageController
- SUSE Linux系统在线安装软件命令zypper参数详解
- Linux下通过rdesktop连接Windows远程桌面
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 本地部署istio多集群(共享控制面)
- elasticsearch unassigned shard
- 【Pytorch 】笔记五:nn 模块中的网络层介绍
- docker浅入深出4
- BFE.dev前端刷题#58. 获取DOM tree高度
- 如何在Tungsten Fabric上整合裸金属服务器(附配置验证过程)
- 逐行阅读Spring5.X源码(三) BeanDefinition的实现类详解,拔高
- 逐行阅读Spring5.X源码(番外篇)BeanDefinition到底有多重要
- 逐行阅读Spring5.X源码(番外篇)AnnotatedBeanDefinitionReader的作用
- 逐行阅读Spring5.X源码(四) BeanFactory——核心容器bean工厂
- 逐行阅读Spring5.X源码(五) 初探BeanFactoryPostProcessor后置处理器,难,特别难。
- 逐行阅读Spring5.X源码(六) ClassPathBeanDefinitionScanner扫描器
- 逐行阅读Spring5.X源码(番外篇)自定义扫描器, Mybatis是如何利用spring完成Mapper扫描的
- 逐行阅读Spring5.X源码(七)扫描和注册神器 ConfigurationClassPostProcessor ,学此类者,胜过学九阳神功!胆小勿入!
- 「Mysql索引原理(三)」Mysql中的Hash索引原理