视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
何谓SQLSERVER参数嗅探
2020-11-09 07:26:26 责编:小采
文档


何谓SQLSERVER参数嗅探 大家听到 嗅探 这个词应该会觉得跟黑客肯定有关系吧,使用工具嗅探一下参数,然后截获,脱裤o(_)o 。 事实上,我觉得大家太敏感了,其实这篇文章跟数据库安全没有什么关系,实际上跟数据库性能调优有关 相信大家有泡SQLSERVER论坛的话

何谓SQLSERVER参数嗅探

大家听到“嗅探”这个词应该会觉得跟黑客肯定有关系吧,使用工具嗅探一下参数,然后截获,脱裤o(∩_∩)o 。

事实上,我觉得大家太敏感了,其实这篇文章跟数据库安全没有什么关系,实际上跟数据库性能调优有关

相信大家有泡SQLSERVER论坛的话不多不少应该都会见过“参数嗅探”这几个字

这里有三篇帖子都是讲述参数嗅探的

http://social.msdn.microsoft.com/Forums/zh-CN/sqlserverzhchs/thread/caccb7f3-8366-4954-8f8a-145eb6bca9dd

http://msdn.microsoft.com/zh-cn/magazine/ee2312.aspx

http://social.msdn.microsoft.com/Forums/zh-CN/sqlserverzhchs/thread/bfbe54de-ac00-49e9-a83b-f97a60bf74ef


下面我给出一个测试数据库的备份文件,里面有一些表和一些测试数据 ,大家可以去下载,因为我下面用的测试表都是这个数据库里的

只需要还原数据库就可以了,这个数据库是SQL2005版本的,数据库名:AdventureWorks

下面只需要用到三张表,表里面有索引:

[Production].[Product]
[SalesOrderHeader_test]
[SalesOrderDetail_test]

数据库下载链接:AdventureWorks_Full_backup_2013-3-4.bak

其实简单来讲,参数嗅探我的很通俗的解释就是:SQLSERVER用鼻子嗅不到具体参数是多少

所以他不能选择最合适的执行计划去执行你的查询,所以参数嗅探是一个不好的现象。


想真正了解参数嗅探,大家可以先创建下面两个存储过程

存储过程一:

 1 USE [AdventureWorks]
 2 GO
 3 DROP PROC Sniff
 4 GO
 5 CREATE PROC Sniff(@i INT)
 6 AS
 7 SELECT COUNT(b.[SalesOrderID]),SUM(p.[Weight])
 8 FROM [dbo].[SalesOrderHeader_test] a
 9 INNER JOIN [dbo].[SalesOrderDetail_test] b
10 ON a.[SalesOrderID]=b.[SalesOrderID]
11 INNER JOIN [Production].[Product] p
12 ON b.[ProductID]=p.[ProductID]
13 WHERE a.[SalesOrderID]=@i
14 GO

存储过程二:

 1 USE [AdventureWorks]
 2 GO
 3 DROP PROC Sniff2
 4 GO
 5 CREATE PROC Sniff2(@i INT)
 6 AS
 7 DECLARE @j INT
 8 SET @j=@i
 9 SELECT COUNT(b.[SalesOrderID]),SUM(p.[Weight])
10 FROM [dbo].[SalesOrderHeader_test] a
11 INNER JOIN [dbo].[SalesOrderDetail_test] b
12 ON a.[SalesOrderID]=b.[SalesOrderID]
13 INNER JOIN [Production].[Product] p
14 ON b.[ProductID]=p.[ProductID]
15 WHERE a.[SalesOrderID]=@j
16 GO

然后请做下面这两个测试

测试一:

 1 --测试一:
 2 USE [AdventureWorks]
 3 GO
 4 DBCC freeproccache
 5 GO
 6 EXEC [dbo].[Sniff] @i = 500000 -- int
 7 --发生编译,插入一个使用nested loops联接的执行计划
 8 GO
 9 
10 EXEC [dbo].[Sniff] @i = 75124 -- int
11 --发生执行计划重用,重用上面的nested loops的执行计划
12 GO

测试二:

 1 --测试二:
 2 
 3 USE [AdventureWorks]
 4 GO
 5 DBCC freeproccache
 6 GO
 7 SET STATISTICS PROFILE ON
 8 EXEC [dbo].[Sniff] @i = 75124 -- int
 9 --发生编译,插入一个使用hash match联接的执行计划
10 GO
11 
12 EXEC [dbo].[Sniff] @i = 50000 -- int
13 --发生执行计划重用,重用上面的hash match的执行计划
14 GO

从上面两个测试可以清楚地看到执行计划重用的副作用。

由于数据分布差别很大参数50000和75124只对自己生成的执行计划有好的性能,

如果使用对方生成的执行计划,性能就会下降。参数50000返回的结果集比较小,

所以性能下降不太严重。参数75124返回的结果集大,就有了明显的性能下降,两个执行计划的差别有近10倍


对于这种因为重用他人生成的执行计划而导致的水土不服现象,SQSERVERL有一个专有名词,叫“参数嗅探 parameter sniffing”

因为语句的执行计划对变量的值很敏感,而导致重用执行计划会遇到性能问题,就是我上面说的

SQLSERVER用鼻子嗅不到具体参数是多少,所以他不能选择最合适的执行计划去执行你的查询


本地变量的影响

那对于有parameter sniffing问题的存储过程,如果使用本地变量,会怎样呢?

下面请看测试3。这次用不同的变量值时,都清空执行计划缓存,迫使其重编译

1 --第一次
2 USE [AdventureWorks]
3 GO
4 DBCC freeproccache
5 GO
6 SET STATISTICS TIME ON
7 SET STATISTICS PROFILE ON
8 EXEC [dbo].[Sniff] @i = 50000 -- int
9 GO

1 --第二次
2 USE [AdventureWorks]
3 GO
4 DBCC freeproccache
5 GO
6 SET STATISTICS TIME ON
7 SET STATISTICS PROFILE ON
8 EXEC [dbo].[Sniff] @i = 75124 -- int
9 GO

1 --第三次
2 USE [AdventureWorks]
3 GO
4 DBCC freeproccache
5 GO
6 SET STATISTICS TIME ON
7 SET STATISTICS PROFILE ON
8 EXEC [dbo].[Sniff2] @i = 50000 -- int
9 GO

1 --第四次
2 USE [AdventureWorks]
3 GO
4 DBCC freeproccache
5 GO
6 SET STATISTICS TIME ON
7 SET STATISTICS PROFILE ON
8 EXEC [dbo].[Sniff2] @i = 75124 -- int
9 GO

看他们的执行计划:


对于第一句和第二句,因为SQL在编译的时候知道变量的值,所以在做EstimateRows的时候,做得非常准确,选择了最适合他们的执行计划

但是对于第三句和第四句,SQLSERVER不知道@j的值是多少,所以在做EstimateRows的时候,不管代入的@i值是多少,

一律给@j一样的预测结果。所以两个执行计划是完全一样的(都是Hash Match)。


参数嗅探的解决办法

参数嗅探的问题发生的频率并不高,他只会发生在一些表格里的数据分布很不均匀,或者用户带入的参数值很不均匀的情况下。

由于篇幅原因我就不具体说了,只是做一些归纳

(1)用exec()的方式运行动态SQL

如果在存储过程里不是直接运行语句,而是把语句带上变量,生成一个字符串,再让exec()这样的命令做动态语句运行,

那SQL就会在运行到这句话的时候,对动态语句进行编译。

这时SQL已经知道了变量的值,会根据生成优化的执行计划,从而绕过参数嗅探问题

 1 --例如前面的存储过程Sniff,就可以改成这样
 2 USE [AdventureWorks]
 3 GO
 4 DROP PROC NOSniff
 5 GO
 6 CREATE PROC NOSniff(@i INT)
 7 AS
 8 DECLARE @cmd VARCHAR(1000)
 9 SET @cmd='SELECT COUNT(b.[SalesOrderID]),SUM(p.[Weight])
10 FROM [dbo].[SalesOrderHeader_test] a
11 INNER JOIN [dbo].[SalesOrderDetail_test] b
12 ON a.[SalesOrderID]=b.[SalesOrderID]
13 INNER JOIN [Production].[Product] p
14 ON b.[ProductID]=p.[ProductID]
15 WHERE a.[SalesOrderID]='
16 EXEC(@cmd+@i)
17 GO

(2)使用本地变量local variable

(3)在语句里使用query hint,指定执行计划

在select,insert,update,delete语句的最后,可以加一个"option()"的子句

对SQLSERVER将要生成的执行计划进行指导。当DBA知道问题所在以后,可以通过加hint的方式,引导

SQL生成一个比较安全的,对所有可能的变量值都不差的执行计划

 1 USE [AdventureWorks]
 2 GO
 3 DROP PROC NoSniff_QueryHint_Recompile
 4 GO
 5 CREATE PROC NoSniff_QueryHint_Recompile(@i INT) 
 6 AS
 7 SELECT COUNT(b.[SalesOrderID]),SUM(p.[Weight])
 8 FROM [dbo].[SalesOrderHeader_test] a
 9 INNER JOIN [dbo].[SalesOrderDetail_test] b
10 ON a.[SalesOrderID]=b.[SalesOrderID]
11 INNER JOIN [Production].[Product] p
12 ON b.[ProductID]=p.[ProductID]
13 WHERE a.[SalesOrderID]=@i
14 OPTION(RECOMPILE)
15 GO

(4)Plan Guide

可以用下面的方法,在原来那个有参数嗅探问题的存储过程“Sniff”上,解决sniffing问题

 1 USE [AdventureWorks]
 2 GO
 3 EXEC [sys].[sp_create_plan_guide]
 4 @name=N'Guide1',
 5 @stmt=N'SELECT COUNT(b.[SalesOrderID]),SUM(p.[Weight])
 6 FROM [dbo].[SalesOrderHeader_test] a
 7 INNER JOIN [dbo].[SalesOrderDetail_test] b
 8 ON a.[SalesOrderID]=b.[SalesOrderID]
 9 INNER JOIN [Production].[Product] p
10 ON b.[ProductID]=p.[ProductID]
11 WHERE a.[SalesOrderID]=@i',
12 @type=N'OBJECT',
13 @module_or_batch=N'Sniff',
14 @params=NULL,
15 @hints=N'option(optimize for(@i=75124))';
16 GO

对于Plan Guide,他还可以使用在一般的语句调优里

终于搞定了,因为要搞测试数据的原因所以搞了很久啊~~

下载本文
显示全文
专题