`
骑猪逛街666
  • 浏览: 144293 次
  • 性别: Icon_minigender_2
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

SQL Server Parameter Sniffing及其改进方法

 
阅读更多

阅读原文请点击

摘要: 上一篇我们谈到,SQL Server 在处理存储过程的时候,为了节省编译时间,是一次编译,多次重用。当第一次运行时代入值产生的执行计划,不适用后续代入的参数时,就产生了parameter sniffing问题。 create procedure Sniff1(@i int) as SELECT count(b.SalesOrderID),sum(p.weight) from [Sale

上一篇我们谈到,SQL Server 在处理存储过程的时候,为了节省编译时间,是一次编译,多次重用。当第一次运行时代入值产生的执行计划,不适用后续代入的参数时,就产生了parameter sniffing问题。

create procedure Sniff1(@i int) as  
SELECT count(b.SalesOrderID),sum(p.weight) from 
[Sales].[SalesOrderHeader] a
inner join [Sales].[SalesOrderDetail] b
on a.SalesOrderID = b.SalesOrderID
inner join Production.Product p
on b.ProductID = p.ProductID
where a.SalesOrderID =@i;
go

DBCC FREEPROCCACHE
exec Sniff1 50000;
exec Sniff1 75124;
go

1

Parameter Sniffing问题发生不频繁,只会发生在数据分布不均匀或者代入参数值不均匀的情况下。现在,我们就来探讨下如何解决这类问题。

1. 使用Exec() 方式运行动态SQL
create procedure Nosniff1(@i int) as  
declare @cmd varchar(1000);
set @cmd = 'SELECT count(b.SalesOrderID),sum(p.weight) from 
[Sales].[SalesOrderHeader] a
inner join [Sales].[SalesOrderDetail] b
on a.SalesOrderID = b.SalesOrderID
inner join Production.Product p
on b.ProductID = p.ProductID
where a.SalesOrderID =';
 exec(@cmd+@i);  
go

 2

exec Nosniff1 50000;

3

exec Nosniff1 75124;

从上述trace中可以看到,在执行查询语句之前,都有SP: CacheInsert事件,SQL Server做了动态编译,根据变量的值,都正确的预估了结果集,给出了不同的执行计划。

2. 使用本地变量

create procedure Nosniff2(@i int) as  
declare @iin int;
set @iin=@i
SELECT count(b.SalesOrderID),sum(p.weight) from 
[Sales].[SalesOrderHeader] a
inner join [Sales].[SalesOrderDetail] b
on a.SalesOrderID = b.SalesOrderID
inner join Production.Product p
on b.ProductID = p.ProductID
where a.SalesOrderID =@iin;
go
exec Nosniff2 50000;

5

6

exec Nosniff2 75124;

 7

8

如上一篇文章所述,使用本地变量,参数值在存储过程语句执行过程中得到,SQL Server在运行时不知道变量的值,会根据一个预估值进行编译,给出一个折中的执行计划。

3. 使用Query Hint,指定执行计划

在 SELECT、DELETE、UPDATE 和 MERGE 语句最后加上OPTION ( <query_hint> [ ,...n ] ),对执行计划进行指导。当数据库管理员知道问题所在时,可以通过hint引导SQL Server生成一个对所有变量都不太差的执行计划。

 

SQL Server 的Query Hint有十几种,具体信息参考https://docs.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-query 。

针对parameter sniffing的问题,有几种hint可以使用。

  • Recompile

Recompile有两种方式,一种是存储过程级别的,一种是语句级别的。具体实现方式如下。

存储过程级别:

create procedure Nosniff_Recompile(@i int)
with recompile as 
SELECT count(b.SalesOrderID),sum(p.weight) from 
[Sales].[SalesOrderHeader] a
inner join [Sales].[SalesOrderDetail] b
on a.SalesOrderID = b.SalesOrderID
inner join Production.Product p
on b.ProductID = p.ProductID
where a.SalesOrderID =@i;
go
exec Nosniff_Recompile 75124;

9

语句级别:

create procedure Nosniff_Recompile2(@i int) as 
SELECT count(b.SalesOrderID),sum(p.weight) from 
[Sales].[SalesOrderHeader] a
inner join [Sales].[SalesOrderDetail] b
on a.SalesOrderID = b.SalesOrderID
inner join Production.Product p
on b.ProductID = p.ProductID
where a.SalesOrderID =@i option(recompile)
go
exec Nosniff_Recompile2 75124;

10

 

这两种recompile方式都可以获得准确的执行计划,区别在于,如果是语句级别的recompile,那么存储过程级别的执行计划重用还是会有的,只有运行到指定语句的时候才会重新编译这个语句。如果存储过程很复杂,存在多个语句,可以指定某个语句中进行重编译,这是一种精细化的调优方式。

  • Optimize for ( @variable_name { UNKNOWN | = literal_constant } [ , ...n ] )

当确认parameter sniffing后,确定了某个参数值重用执行计划会特别慢,那么可以使用optimize for 某个参数,来倾向性的生成执行计划。

create procedure Nosniff_OptimizeFor(@i int) as 
SELECT count(b.SalesOrderID),sum(p.weight) from 
[Sales].[SalesOrderHeader] a
inner join [Sales].[SalesOrderDetail] b
on a.SalesOrderID = b.SalesOrderID
inner join Production.Product p
on b.ProductID = p.ProductID
where a.SalesOrderID =@i option(optimize for(@i=75124))
go
exec Nosniff_OptimizeFor 50000;
exec Nosniff_OptimizeFor 75124;

执行50000时,会生成一个执行计划,不过执行计划是根据75124来预估的,所以执行75124时,重用了执行计划,但是执行效率也不差。

11
  • Plan guid
Exec sp_create_plan_guide
@name='Guide1',
@stmt='
SELECT count(b.SalesOrderID),sum(p.weight) from 
[Sales].[SalesOrderHeader] a
inner join [Sales].[SalesOrderDetail] b
on a.SalesOrderID = b.SalesOrderID
inner join Production.Product p
on b.ProductID = p.ProductID
where a.SalesOrderID =@i',
@type=N'object',
@module_or_batch=N'Sniff1',
@params=NULL,
@hints=N'option(optimize for(@i=75124))';
go

当下次调用存储过程Sniff1时,不管输入的参数是什么,都会根据75124去进行预估,生成执行计划。

阅读原文请点击

分享到:
评论

相关推荐

    SQL Server中存储过程比直接运行SQL语句慢的原因

    在 SQL Server 中,存储过程比直接运行 SQL 语句慢的原因是 Parameter sniffing 问题。Parameter sniffing 是指 SQL Server 在执行存储过程时,使用参数的统计信息来优化执行计划,但这种优化方式有时可能会导致执行...

    SQL Server Statistics Primer

    1. **参数嗅探(Parameter Sniffing)**:这是一个SQL Server特性,它根据首次运行时的参数值来选择执行计划。如果这些初始参数不再反映当前数据分布,可能会导致性能问题。为了避免这种情况,可以通过强制重新编译...

    SQL Server SQL性能优化之参数化

    为了解决这个问题,我们可以考虑调整参数化模式,或者使用动态SQL来避免parameter sniffing。此外,还可以通过优化索引、使用绑定变量、或使用WITH RECOMPILE选项来防止不合适的执行计划被缓存。 总的来说,理解SQL...

    SQL Server中参数化SQL写法遇到parameter sniff ,导致不合理执行计划重用的快速解决方法

    在SQL Server中,参数化SQL是一种...在实际应用中,可以通过监控和分析SQL Server的执行计划来确定哪种方法最适合解决问题。同时,保持良好的数据库设计,如创建合适的索引,也有助于避免或减轻参数嗅探带来的影响。

    SQL语句执行超时引发网站首页访问故障问题

    首先,问题的根源在于SQL Server的参数嗅探(Parameter Sniffing)机制。参数嗅探是SQL Server优化器在编译存储过程或动态SQL时,根据传入的参数值来选择最佳的执行计划。然而,当不同的参数值导致执行计划差异较大...

    Python Penetration Testing Essentials, 2nd Edition

    We then move on to concepts related to website hacking―such as parameter tampering, DDoS, XSS, and SQL injection. By reading this book, you will learn different techniques and methodologies that ...

Global site tag (gtag.js) - Google Analytics