设为首页 收藏本站
查看: 823|回复: 0

[经验分享] SQL Server中参数化SQL写法遇到parameter sniff ,导致不合理执行计划重用的一种解决方案

[复制链接]

尚未签到

发表于 2017-7-13 12:48:23 | 显示全部楼层 |阅读模式
  parameter sniff问题是重用其他参数生成的执行计划,导致当前参数采用该执行计划非最优化的现象。想必熟悉数据的同学都应该知道,产生parameter sniff最典型的问题就是使用了参数化的SQL(或者存储过程中使用了参数化)写法,如果存在数据分布不均匀的情况下,正常情况下生成的执行计划,在传入在分布数据较多的参数的情况下,重用了正常参数生成的执行计划,而这种缓存的执行计划并非适合当前参数的一种情况。
  这种情况,在实际业务中,出现的频率还是比较高的,因为存储过程一般都是采用参数化的写法,这时,遇到分布不均匀的数据参数时,parameter sniff现象就出现了,这种问题还是比较让人头疼的。
  具体parameter sniff产生的原因,我就不做过多的解释了,解释这个就显得太low了
  我举个简单的例子,模拟一下这个现象,说明参数化的存存储过程是怎么写的,存在哪些问题,又如何解决parameter sniff问题,
  先创建一个测试环境:



create table ParameterSniffProblem
(
id int identity(1,1),
CustomerId int,
OrderId int,
OrederStatus int,
CreateDate Datetime,
Remark varchar(200)
)
declare @i int = 0
while @i<500000
begin
INSERT INTO ParameterSniffProblem values (@i%10000,@i,RAND()*10,GETDATE()-RAND()*100,NEWID())
set @i=@i+1
end

--假如某一个客户有非常多的订单,模拟数据分布不均匀的情况
INSERT INTO ParameterSniffProblem values (6666,RAND()*100000,1,GETDATE()-RAND()*100,NEWID())
GO 100000
--创建正常的索引
CREATE CLUSTERED INDEX IDX_CreateDate on ParameterSniffProblem(CreateDate)
CREATE INDEX IDX_CustomerId ON ParameterSniffProblem(CustomerId)
  参数化存储过程的写法:
  在编写存储过程的时候,我们一般建议采用参数化的写法,目的是为了减少存储过程的编译和加强执行计划缓存的重用
  大概是这样子的



CREATE PROCEDURE [dbo].ParameterSniffTest   
(   
@p_CustomerId int,
@p_Status int,
@p_FromDate datetime,
@p_ToDate datetime
)
AS   
BEGIN
SET NOCOUNT ON  
DECLARE
@Parm               NVARCHAR(MAX),
@sqlcommand         NVARCHAR(MAX) = N''
        SET @sqlcommand = 'SELECT * FROM ParameterSniffProblem WHERE 1=1'
     IF(@p_CustomerId IS NOT NULL)
SET @sqlcommand = CONCAT(@sqlcommand,'AND CustomerId=@p_CustomerId ')
IF(@p_Status IS NOT NULL)
SET @sqlcommand = CONCAT(@sqlcommand,'AND OrederStatus=@p_Status ')
IF(@p_FromDate IS NOT NULL)
SET @sqlcommand = CONCAT(@sqlcommand,'AND CreateDate>=@p_FromDate ')
IF(@p_ToDate IS NOT NULL)
SET @sqlcommand = CONCAT(@sqlcommand,'AND CreateDate<=@p_ToDate ')

    SET @Parm= '@p_CustomerId int,
@p_Status   int,
@p_FromDate  datetime,
@p_ToDate   datetime '
    EXEC sp_executesql @sqlcommand,@Parm,
@p_CustomerId    =    @p_CustomerId,
@p_Status        =    @p_Status,
@p_FromDate      =    @p_FromDate,
@p_ToDate        =    @p_ToDate
END
GO
  Parameter Sniff问题:
  这就潜在一个parameter sniff问题,
  比如我查询用户ID=100的订单信息,一个正常的分布的数据,存储过程第一次编译,这个执行计划完全没有问题,
DSC0000.png

  如果我接着改变参数执行查询用户6666的信息,一个分布及其不均匀的数据,但是因为重用上面缓存的执行计划,就出现parameter sniff问题了,这个执行计划显然是不合理的
  IO就不看了,刻意造的例子
DSC0001.png

  如果我清空执行计划缓存,
  重新执行上述查询,因为有了重编译,执行计划就是不这个样子,对于CustomerID=6666这个参数来说,显然走全表扫描代价要更小一点
DSC0002.png

  想必这是一个开发中常见的问题给,
  我们参数化SQL就是为了让不同参数的查询重用执行计划,
  但是很不幸,数据分布不均匀的时候,重用执行计划恰恰又给数据库造成了伤害,
  上例中,如果是正常参数重用了分布较多数据的执行计划,比如命名可以用到索引,结果是表扫描,后果会更严重。
  那么,既想要尽可能的重用执行计划,又要避免因为执行计划重用产生parameter sniff问题,怎么办?
  我们知道问题在于@p_CustomerId身上,那么可不可以对有可能产生parameter sniff问题的@p_CustomerId不做参数化,直接拼凑在SQL中,
  如果@p_CustomerId变化了就重编译SQL,也就是对传入进来的@p_CustomerId重编译
  如果是@p_CustomerId不变,其他参数有变化,比如这里时间字段的变化,还可以享受参数化带来的执行计划重用的好处
  也就是这样处理 @p_CustomerId这个参数,直接把@p_CustomerId以字符串的方式平凑在SQL语句中,
  这样的话,就相当于即席查询了,不通过参数化的方式给CustomerId这个查询条件字段赋值
  IF(@p_CustomerId IS NOT NULL)
   SET @sqlcommand = CONCAT(@sqlcommand,'AND CustomerId= ',@p_CustomerId)
  这样再去执行存储过程的时候,
  带入@p_CustomerId=1的时候,执行IDX_CustomerId的index seek
   DSC0003.png
  带入@p_CustomerId=6666的时候,重编译,执行计划是全表扫描,避免重用上面生成的执行计划,造成不合理的执行方式对效率以及数据库服务器资源的消耗
   DSC0004.png
  这样会尽可能的减少parameter sniff问题带来的影响,当缓存了@p_CustomerId=1的执行计划的时候,
  再次传入@p_CustomerId=1,其他条件有较小的变化,比如时间字段上有改动,依然可以重用缓存的执行计划,避免重编译带来的影响
  结论:
  这种方式于处理parameter sniff问题,当然不是完美的,肯定也有问题,我当然知道一旦@p_CustomerId不同就要重编译
  肯定会因为@p_CustomerId参数值不同,这样的话,不可避免地增加了重编译的机会,
  但是却不会因为不合理的执行计划重用,带来的parameter sniff问题
  要知道一旦产生parameter sniff问题,大量的查询用到不合理的执行计划,会对整个服务器产生非常严重的影响,比如可能会产生大量的IO等
  同时存在一个好处,
  比如第一次传入@p_CustomerId=1,
  再次传入@p_CustomerId=1,其他条件有较小的变化,比如时间字段上有改动,依然可以重用缓存的执行计划,避免重编译带来的影响
  当然我这里只是一个简单的例子,实际应用中远远比这个复杂
  比如分布的特别的多的数据有两个特点,第一分布的标示不仅仅只有一个,第二分布不均的数据是动态的,
  有可能第一季度是A这部分数据占据大多数,有可能是第二季度B数据占绝大多数
  所以很难采用Plan Guide的方式解决parameter sniff问题
  这种方式可以在一定程度上也能够重用缓存的执行计划,可以减少(但不可避免)重编译的次数
  同时,这种方式与拼凑一个SQL字符串执行的即席查询方式相比,同时还可以利用参数化带来的其他好处,比如SQL注入等等
  总结:
  parameter sniff问题的解决方式有很多,不一一啰嗦了
  最典型的就是强制重编译,
  或者使用EXEC执行一个拼凑出来的字符串,这种方式属于Adhoc查询
  或者查询提示,
  或者是使用本地变量,
  或者使用Plan Guide等等等等,
  每种方式都有他的局限性,至少到目前为止,还没有一种十全十美的方式来解决parameter sniff问题
  遇到问题,解决方法有很多种,以最小的代价解决问题才是王道。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-393420-1-1.html 上篇帖子: 理解SQL Server是如何执行查询的 (2/3) 下篇帖子: SQL Server存储过程同时返回分页结果集和总数
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表