欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

性能调优5:执行计划

程序员文章站 2022-09-04 15:45:29
查询优化器基于当前的统计信息和参数,衡量开销之后,选择“最优”的执行计划,需要注意的是,“最优”是相对的,优化器不可能穷举所有的执行计划来评估其开销,这个“最优”的标准是对当前参数和当前的统计信息来说的,优化器从生成的备选执行计划中选择开销最小的。由于执行计划的编译和生成是很耗费资源和时间的,因此, ......

查询优化器基于当前的统计信息和参数,衡量开销之后,选择“最优”的执行计划,需要注意的是,“最优”是相对的,优化器不可能穷举所有的执行计划来评估其开销,这个“最优”的标准是对当前参数和当前的统计信息来说的,优化器从生成的备选执行计划中选择开销最小的。由于执行计划的编译和生成是很耗费资源和时间的,因此,sql server会把生成的任一执行计划缓存起来,以便重用。

由于关系表的数据和结构可能发生改变,数据更新会导致统计信息过时,而之前的参数可能不具有代表性,使得已生成的执行计划不能代表其他参数值,导致查询性能低下。因此,应当监控执行计划的性能,当发现参数嗅探问题时,应该及时修改代码以重编译;当发现统计信息过期时,应及时更新统计信息等。

一,缓存机制

sql server使用特定的缓存机制,以重用第一次执行查询时生成的执行计划,总的来说,sql server内部有以下四种执行计划缓存机制:

  • ad hoc 查询缓存
  • 参数化ad hoc查询缓存
  • sys.sp_executesql 执行的查询,是一种参数化的查询语句
  • 存储过程

对于adhoc查询的缓存,是sql server自动进行的,用户不能干预,而后两种是用户可以干预的,用户可以通过优化代码来复用“模板化”的查询。所谓模板化语句,是指除了个别的常量发生变化之外,语句主体不变,可以把变化的常量作为一个参数,不变的语句主体作为一个模板来处理,sql server优化器把这个模板编译成执行计划,传入不同的参数会使用相同的执行计划。

1,ad hoc查询缓存

对于任意一个ad hoc查询,sql server都会缓存它的执行计划,但是,只有当批处理语句的文本完全匹配时,才会复用已缓存的执行计划,完全匹配的处理过程是:

  • sql server根据批处理语句的文本计算出一个hash值,对后续的ad hoc查询的文本同样计算hash值,当两个hash值相同时,说明两个批处理的文本完全相同,相当于同一个查询的重复执行,sql server优化器会复用已缓存的执行计划。
  • 如果ad hoc查询的文本有任意一个字符发生变化(比如,大写字符变小写字符,不同的换行,多了一个空格等),都会导致计算出的hash值不同,进而不能复用执行计划。也就是说,ad hoc查询的文本必须完全匹配才能复用执行计划。

大量的ad hoc查询缓存会占用计划缓存的空间,这些缓存可能只会被使用一次,以后再也不会被使用。如果数据库系统中存在大量的一次性查询语句,应设置server 级别的性能优化选项:optimize for ad hoc wrokloads。

“针对即席工作负载进行优化”是一个server级别的性能优化选项,用于提高包含许多临时批处理的工作负载的计划缓存的效率,如果把该选项设置为true,则数据库引擎在首次编译批处理时只保留计划缓存中的一个存根,而不是存储整个执行计划。当再次调用该批处理时,数据库引擎识别出该批处理在之前被执行过,进而从计划缓存中删除该执行计划的存根,并把完全编译的执行计划添加到计划缓存中。当非参数化的ad-hoc查询较多时,可以避免计划缓存存储过多的不会被复用的执行计划。

性能调优5:执行计划

2,参数化ad-hoc

sql server 自主决定是否把查询中的常量作为参数来对待,除了常量不同之外,其他语句主体都相同,这就是这个查询语句的模板,不同的参数使用相同的执行计划。

例如,对于以下两个查询语句,除了常量1和2不同之外,其他语句都相同,

select id, name from dbo.users where id=1
select id, name from dbo.users where id=2

sql server对该语句做参数化处理,得到模板,只要语句符合该模板,优化器就复用已缓存的执行计划。

select id, name from dbo.users where id=@id

3,prepared 查询缓存

用户使用sys.sp_executesql 控制参数和模板,只要模板相同,而参数不同,都可以复用已缓存的执行计划。

4,存储过程

用户创建的存储过程,在第一次执行时,编译和生成执行计划,并缓存到计划缓存中,当下次调用相同的存储过程,即使使传递的参数不同,sql server都会复用执行计划。

二,参数嗅探

参数嗅探是指在创建存储过程,或者参数化查询的执行计划时,根据传入的参数进行预估并生成执行计划。sql server生成的执行计划对当前参数来说是最优的,而对其他大多数参数来说,是非常低效的。有些时候,针对一个查询的第一次传参,已经产生了一个执行计划,当后续传参时,由于存在对应参数的数据分布等问题,导致原有的执行计划无法高效地响应查询请求,这就出现参数嗅探问题。

参数嗅探的本质是优化器根据参数来生成的执行计划不是最优的,导致优化器在复用执行计划时,语句的查询性能变得十分低下。对于参数嗅探问题,必须重新生成执行计划,可以使用语句重编译,编译提示(optimize for)等功能来避免。

三,影响执行计划复用的因素

sql server不会永久保存计划的缓存,并且存在缓存中的执行计划也不会永久不变,每个计划都会有一个age值,当sql server探测到内存压力时,会触发lazy writer进程,用于清空所有的脏页,释放数据缓存。当扫面到计划缓存时,会降低age值,当复用一次计划时,会增加age值。当系统遇到内存压力,或age值降到0时,执行计划会被移除内存。

除了这两个条件之外,当遇到下面的条件时,执行计划一会被移除内存,被重新编译:

  • 查询引用的基础表的结构被更改
  • 查询引用的索引被更改或被删除
  • 查询引用的统计信息被更新
  • 执行计划被强制重新编译(详见本问第四小节)
  • 单一查询中混合了ddl和dml操作,也称为延迟编译
  • 在查询中修改set选项
  • 查询所用到的临时表的结构被修改
  • 等等

在执行计划执行过程中,执行计划被重新编译,是优化器根据表结构,索引结构和统计信息做出优化的结构,目的是为了避免继续使用不合适的执行计划。

四,强制重新编译执行计划

修改存储过程,触发器等模块(module)能够使其执行计划重新编译,除此之外,还有其他方法,能够强制重新编译执行计划

1,标记,下次重新编译

使用该存储过程,标记一个执行模块(sp,trigger,user-defined function)在下次执行时,重新编译执行计划

sys.sp_recompile [ @objname = ] 'object'

2,不复用执行计划

在创建存储过程时,使用with recompile 选项,在每次执行sp时,都重新编译,使用新的执行计划。

create procedure dbo.usp_procname 
    @parameter_name varchar(30) = 'parameter_default_value'
with recompile

3,执行时重新编译

在执行存储过程时,重新编译存储过程的执行计划

exec dbo.usp_procname @parameter_name='parameter_value' 
with recompile

4,语句级别的重新编译

在sp中,使用查询选项 option(recompile),只重新编译该语句级别的执行计划

select column_name_list
from dbo.tablename
option(recompile)

sql server在执行查询之后,查询提示(recompile)指示存储引擎将计划缓存抛弃,在下次执行存储过程时,强制查询优化器重新编译,生成新的执行计划。在重新编译时,sql server 优化器使用当前的变量值生成新的计划缓存。

五,控制执行计划

优化器会根据查询选择执行计划,选择索引,表关联算法等,但是,当发现优化器选择了低效的执行计划时,可以使用hint来控制执行计划,sql server提供了三种类型的hint:

  • 查询提示(query hint):告知优化器在整个查询过程中都应用某个提示,
  • 关联提示(join hint):告知优化器在关联时使用特定的关联算法
  • 表提示(table hint):告知优化器使用表扫描,还是表上特定的索引

1,查询提示

使用option来设置查询提示,

  • 用于group by 聚合,可以控制分组的算法:hash group 和order group
  • 用于控制关联的算法, option(hash join)
  • 通常情况下,优化器决定表关联的顺序,可以使用force order选项,使优化器按照join的顺序来关联, option(force order)
  • 使用maxdop来确定语句执行的最大并发度,option(maxdop 1),取消并发执行。
  • 按照指定的参数来优化 option(optimize for (@para_name= constant_value))

2,关联提示

在 join关键字前面使用loop,merge和hash来控制关联的算法

3,表提示

在引用的表名后面,通过with()来设置表提示 table_name with(hints),

当使用索引时,使用 with(index(index_name))来设置,

 

参考文档: