SQLSERVER参数嗅探问题的示例分析
小编给大家分享一下SQLSERVER参数嗅探问题的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!
下面测试数据库的备份文件,里面有一些表和一些测试数据 ,因为我下面用的测试表都是这个数据库里的
只需要还原数据库就可以了,这个数据库是SQL2005版本的,数据库名:AdventureWorks
下面只需要用到三张表,表里面有索引:
[Production].[Product] [SalesOrderHeader_test] [SalesOrderDetail_test]
数据库下载链接:AdventureWorks
其实简单来讲,参数嗅探我的很通俗的解释就是:SQLSERVER用鼻子嗅不到具体参数是多少
所以他不能选择最合适的执行计划去执行你的查询,所以参数嗅探是一个不好的现象。
想真正了解参数嗅探,大家可以先创建下面两个存储过程
存储过程一:
USE[AdventureWorks]GODROPPROCSniffGOCREATEPROCSniff(@iINT)ASSELECTCOUNT(b.[SalesOrderID]),SUM(p.[Weight])FROM[dbo].[SalesOrderHeader_test]aINNERJOIN[dbo].[SalesOrderDetail_test]bONa.[SalesOrderID]=b.[SalesOrderID]INNERJOIN[Production].[Product]pONb.[ProductID]=p.[ProductID]WHEREa.[SalesOrderID]=@iGO
存储过程二:
复制代码代码如下:1USE[AdventureWorks]2GO3DROPPROCSniff24GO5CREATEPROCSniff2(@iINT)6AS7DECLARE@jINT8SET@j=@i9SELECTCOUNT(b.[SalesOrderID]),SUM(p.[Weight])10FROM[dbo].[SalesOrderHeader_test]a11INNERJOIN[dbo].[SalesOrderDetail_test]b12ONa.[SalesOrderID]=b.[SalesOrderID]13INNERJOIN[Production].[Product]p14ONb.[ProductID]=p.[ProductID]15WHEREa.[SalesOrderID]=@j16GO
然后请做下面这两个测试
测试一:
--测试一:USE[AdventureWorks]GODBCCfreeproccacheGOEXEC[dbo].[Sniff]@i=500000--int--发生编译,插入一个使用nestedloops联接的执行计划GOEXEC[dbo].[Sniff]@i=75124--int--发生执行计划重用,重用上面的nestedloops的执行计划GO
测试二:
--测试二:USE[AdventureWorks]GODBCCfreeproccacheGOSETSTATISTICSPROFILEONEXEC[dbo].[Sniff]@i=75124--int--发生编译,插入一个使用hashmatch联接的执行计划GOEXEC[dbo].[Sniff]@i=50000--int--发生执行计划重用,重用上面的hashmatch的执行计划GO
从上面两个测试可以清楚地看到执行计划重用的副作用。
由于数据分布差别很大参数50000和75124只对自己生成的执行计划有好的性能,
如果使用对方生成的执行计划,性能就会下降。参数50000返回的结果集比较小,
所以性能下降不太严重。参数75124返回的结果集大,就有了明显的性能下降,两个执行计划的差别有近10倍
对于这种因为重用他人生成的执行计划而导致的水土不服现象,SQSERVERL有一个专有名词,叫“参数嗅探 parameter sniffing”
因为语句的执行计划对变量的值很敏感,而导致重用执行计划会遇到性能问题,就是我上面说的
“
SQLSERVER用鼻子嗅不到具体参数是多少,所以他不能选择最合适的执行计划去执行你的查询
”
本地变量的影响
那对于有parameter sniffing问题的存储过程,如果使用本地变量,会怎样呢?
下面请看测试3。这次用不同的变量值时,都清空执行计划缓存,迫使其重编译
--第一次USE[AdventureWorks]GODBCCfreeproccacheGOSETSTATISTICSTIMEONSETSTATISTICSPROFILEONEXEC[dbo].[Sniff]@i=50000--intGO
--第二次USE[AdventureWorks]GODBCCfreeproccacheGOSETSTATISTICSTIMEONSETSTATISTICSPROFILEONEXEC[dbo].[Sniff]@i=75124--intGO
--第三次USE[AdventureWorks]GODBCCfreeproccacheGOSETSTATISTICSTIMEONSETSTATISTICSPROFILEONEXEC[dbo].[Sniff2]@i=50000--intGO
--第四次USE[AdventureWorks]GODBCCfreeproccacheGOSETSTATISTICSTIMEONSETSTATISTICSPROFILEONEXEC[dbo].[Sniff2]@i=75124--intGO
看他们的执行计划:
对于第一句和第二句,因为SQL在编译的时候知道变量的值,所以在做EstimateRows的时候,做得非常准确,选择了最适合他们的执行计划
但是对于第三句和第四句,SQLSERVER不知道@j的值是多少,所以在做EstimateRows的时候,不管代入的@i值是多少,
一律给@j一样的预测结果。所以两个执行计划是完全一样的(都是Hash Match)。
参数嗅探的解决办法
参数嗅探的问题发生的频率并不高,他只会发生在一些表格里的数据分布很不均匀,或者用户带入的参数值很不均匀的情况下。
由于篇幅原因我就不具体说了,只是做一些归纳
(1)用exec()的方式运行动态SQL
如果在存储过程里不是直接运行语句,而是把语句带上变量,生成一个字符串,再让exec()这样的命令做动态语句运行,
那SQL就会在运行到这句话的时候,对动态语句进行编译。
这时SQL已经知道了变量的值,会根据生成优化的执行计划,从而绕过参数嗅探问题
--例如前面的存储过程Sniff,就可以改成这样USE[AdventureWorks]GODROPPROCNOSniffGOCREATEPROCNOSniff(@iINT)ASDECLARE@cmdVARCHAR(1000)SET@cmd='SELECTCOUNT(b.[SalesOrderID]),SUM(p.[Weight])FROM[dbo].[SalesOrderHeader_test]aINNERJOIN[dbo].[SalesOrderDetail_test]bONa.[SalesOrderID]=b.[SalesOrderID]INNERJOIN[Production].[Product]pONb.[ProductID]=p.[ProductID]WHEREa.[SalesOrderID]='EXEC(@cmd+@i)GO
(2)使用本地变量local variable
(3)在语句里使用query hint,指定执行计划
在select,insert,update,delete语句的最后,可以加一个"option(<query_hint>)"的子句
对SQLSERVER将要生成的执行计划进行指导。当DBA知道问题所在以后,可以通过加hint的方式,引导
SQL生成一个比较安全的,对所有可能的变量值都不差的执行计划
USE[AdventureWorks]GODROPPROCNoSniff_QueryHint_RecompileGOCREATEPROCNoSniff_QueryHint_Recompile(@iINT)ASSELECTCOUNT(b.[SalesOrderID]),SUM(p.[Weight])FROM[dbo].[SalesOrderHeader_test]aINNERJOIN[dbo].[SalesOrderDetail_test]bONa.[SalesOrderID]=b.[SalesOrderID]INNERJOIN[Production].[Product]pONb.[ProductID]=p.[ProductID]WHEREa.[SalesOrderID]=@iOPTION(RECOMPILE)GO
(4)Plan Guide
可以用下面的方法,在原来那个有参数嗅探问题的存储过程“Sniff”上,解决sniffing问题
USE[AdventureWorks]GOEXEC[sys].[sp_create_plan_guide]@name=N'Guide1',@stmt=N'SELECTCOUNT(b.[SalesOrderID]),SUM(p.[Weight])FROM[dbo].[SalesOrderHeader_test]aINNERJOIN[dbo].[SalesOrderDetail_test]bONa.[SalesOrderID]=b.[SalesOrderID]INNERJOIN[Production].[Product]pONb.[ProductID]=p.[ProductID]WHEREa.[SalesOrderID]=@i',@type=N'OBJECT',@module_or_batch=N'Sniff',@params=NULL,@hints=N'option(optimizefor(@i=75124))';GO
以上是“SQLSERVER参数嗅探问题的示例分析”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。