SQL Server 查询优化(测试02)参数嗅探-执行计划选择

最近常看到"参数嗅探"这个词,看了几篇文章,于是就自己摸索做个测试来加深印象!

去官网下载了数据库:AdventureWorks2012

直接测试吧!
找几个熟悉的表关联起来,用ProductID作为条件找到两个ID返回行数相差较大的值.
ProductID=870(4688行)	ProductID=897(2行)

【测试一】

--先清空计划缓存
DBCC FREEPROCCACHE

--执行前先打开计数器监控查看(分开执行以下查询)
select sdh.SalesOrderID,sdh.SalesOrderNumber,P.ProductID,p.Name,sod.LineTotal
from [Sales].[SalesOrderHeader] sdh
inner join [Sales].[SalesOrderDetail] sod on sdh.SalesOrderID = sod.SalesOrderID
inner join [Production].[Product] p on sod.ProductID = p.ProductID
where P.ProductID =870

select sdh.SalesOrderID,sdh.SalesOrderNumber,P.ProductID,p.Name,sod.LineTotal
from [Sales].[SalesOrderHeader] sdh
inner join [Sales].[SalesOrderDetail] sod on sdh.SalesOrderID = sod.SalesOrderID
inner join [Production].[Product] p on sod.ProductID = p.ProductID
where P.ProductID =897

先看计数器,有两个绿色的峰值为1.就是上面分别执行时发生的编译次数.

--	查看缓存对象执行类型:Adhoc(即时查询)
SELECT cacheobjtype,objtype,refcounts,usecounts,[sql]
FROM sys.syscacheobjects
WHERE [sql] LIKE '%SalesOrderID%' AND [sql] NOT LIKE '%sys%'

--	再用视图查看缓存查询计划和计划大小
SELECT refcounts,usecounts,cacheobjtype,size_in_bytes,[text],query_plan
FROM sys.dm_exec_cached_plans
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
CROSS APPLY sys.dm_exec_query_plan(plan_handle)
WHERE [text] LIKE '%SalesOrderID%' AND [text] NOT LIKE '%sys%'

可以看到生成了两个不同的查询计划(query_plan),并且占用了缓存(size_in_bytes).

以上这种写法的优缺点是:

缺点: 如果查询条件值发生变化,每次都会作为新的查询语句编译第一次,不仅消耗CPU,而且生成新的查询计划也会占用缓存.

优点:
每次执行计划都是最优的

【测试二】

现在换成带参数的形式.

--先清空计划缓存
DBCC FREEPROCCACHE

--	ProductID=870(4688行)	ProductID=897(2行)
DECLARE @ProductID INT
SET @ProductID = 870
select sdh.SalesOrderID,sdh.SalesOrderNumber,P.ProductID,p.Name,sod.LineTotal
from [Sales].[SalesOrderHeader] sdh
inner join [Sales].[SalesOrderDetail] sod on sdh.SalesOrderID = sod.SalesOrderID
inner join [Production].[Product] p on sod.ProductID = p.ProductID
where P.ProductID [email protected]

DECLARE @ProductID INT
SET @ProductID = 897
select sdh.SalesOrderID,sdh.SalesOrderNumber,P.ProductID,p.Name,sod.LineTotal
from [Sales].[SalesOrderHeader] sdh
inner join [Sales].[SalesOrderDetail] sod on sdh.SalesOrderID = sod.SalesOrderID
inner join [Production].[Product] p on sod.ProductID = p.ProductID
where P.ProductID [email protected]

看计数器,同样有两个绿色的峰值为1.发生了2次编译

--	再用视图查看缓存查询计划和计划大小
SELECT refcounts,usecounts,cacheobjtype,size_in_bytes,[text],query_plan
FROM sys.dm_exec_cached_plans
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
CROSS APPLY sys.dm_exec_query_plan(plan_handle)
WHERE [text] LIKE '%SalesOrderID%' AND [text] NOT LIKE '%sys%'

这会可以看到生成了两个相同的查询计划(query_plan),缓存大小(size_in_bytes)也就相同了.

还有另一点不同之处就是,执行计划分两部分执行,第一部分参数赋值,第二部分查询语句.

因此第二部分才用了相同的查询计划.

以上这种写法的优缺点是:

缺点: 如果查询条件值发生变化,每次都会作为新的查询语句编译第一次,不仅消耗CPU,而且生成新的查询计划也会占用缓存.

还有就是,由于查询计划相同.当返回行数相差较大.有的查询性能并不是较好的.

优点: 当返回数据量都差不多的时候是较好的,查询优化器根据参数估计一个较好的查询计划,有利于对查询计划进行控制.

(但是比较发现,这种写法比上一种还差!最后再测试)

【测试三】

--	这时把执行语句放到存储过程
CREATE PROCEDURE P_Test(@ProductID INT)
AS
BEGIN
select sdh.SalesOrderID,sdh.SalesOrderNumber,P.ProductID,p.Name,sod.LineTotal
from [Sales].[SalesOrderHeader] sdh
inner join [Sales].[SalesOrderDetail] sod on sdh.SalesOrderID = sod.SalesOrderID
inner join [Production].[Product] p on sod.ProductID = p.ProductID
where P.ProductID [email protected]
END

--	ProductID=870(4688行)	ProductID=897(2行)
--	执行存储过程
DBCC FREEPROCCACHE
EXEC P_Test @ProductID = 870
EXEC P_Test @ProductID = 897

--	查看缓存对象执行类型:Proc(存储过程)
SELECT cacheobjtype,objtype,refcounts,usecounts,[sql]
FROM sys.syscacheobjects
WHERE [sql] LIKE '%SalesOrderID%' AND [sql] NOT LIKE '%sys%'

SELECT refcounts,usecounts,cacheobjtype,size_in_bytes,[text],query_plan
FROM sys.dm_exec_cached_plans
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
CROSS APPLY sys.dm_exec_query_plan(plan_handle)
WHERE [text] LIKE '%SalesOrderID%' AND [text] NOT LIKE '%sys%'

这时发现,只有1个缓存计划!无论参数怎么改变都是只缓存一个查询计划,这样就省去了内存的占用.

但是这个方法的优缺点就更明显了.

这种写法的优缺点是:

缺点: 如果查询条件值发生变化,每次都会编译1次,消耗CPU.

最重要的缺点是,查询计划的产生,是以第一次执行存储过程所传递的参数值来确定的!

也就是说,在存储过程创建后,传递参数首次执行存储过程,该参数返回的行数或多或少都会影响到执行计划的永久确定.

DBCC FREEPROCCACHE
--情况计划缓存

EXEC P_Test @ProductID = 870
--现在换870先执行

EXEC P_Test @ProductID = 897
--刚才为897首次执行存储过程

执行后再看查询计划,又是不一样了!

所以这点要注意,为什么同样的存储过程,表统计信息没问题,但是有的查询快,有的慢.

跟踪把具体语句查出来运行又正常,就如同上面【测试一】一样。

优点: 省下了内存!(不过内存一般用不了多少)

【测试四】

--	这时把执行语句放到存储过程
CREATE PROCEDURE P_Test2(@ProductID INT)
AS
BEGIN
DECLARE @ID INT
SET @ID = @ProductID --区别在这里
select sdh.SalesOrderID,sdh.SalesOrderNumber,P.ProductID,p.Name,sod.LineTotal
from [Sales].[SalesOrderHeader] sdh
inner join [Sales].[SalesOrderDetail] sod on sdh.SalesOrderID = sod.SalesOrderID
inner join [Production].[Product] p on sod.ProductID = p.ProductID
where P.ProductID [email protected]
END

--	ProductID=870(4688行)	ProductID=897(2行)
DBCC FREEPROCCACHE
EXEC P_Test2 @ProductID = 870
EXEC P_Test2 @ProductID = 897

SELECT refcounts,usecounts,cacheobjtype,size_in_bytes,[text],query_plan
FROM sys.dm_exec_cached_plans
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
CROSS APPLY sys.dm_exec_query_plan(plan_handle)
WHERE [text] LIKE '%SalesOrderID%' AND [text] NOT LIKE '%sys%'

这种方法优缺点与【测试三】几乎一样,唯一不同的是,首次生成的执行计划不受参数影响。

如下两个存储过程,刚创建完存储过程后,不管谁先执行,查询计划都是一样的!

EXEC P_Test2 @ProductID = 870

EXEC P_Test2 @ProductID = 897

这里就真正用到了所谓的“参数嗅探”!以为优化引擎首次确定查询计划时,并不知道执行的参数值是什么。

因此只嗅探到传递的参数,系统就是根据参数确定了存储过程的查询计划。

这里也有不好的一点,就是参数返回多少也可能影响到性能。

---------------------------------------------------------------------

---------------------------------------------------------------------

总结: 

以上几种都有优缺点,最不好的就是【测试二】那种。

还有一个现象,就是上面的所有测试,个人在性能监视器中都没有发现“重编译”的情况,每次都只有“编译”。

虽然编译包括重编译,但是重编译都没出现过一次。除非显示让语句重编译(如 option(recompile))才出现。

测试一:最佳,每次都会生成新的计划缓存

测试二:不好,同样缓存计划,返回结果集较大时性能不一样

测试三:省缓存,随着数据量增长,存储过程最好重新编译

测试四:省缓存,查询计划固定,更改不了。

最后总体测试对表以上这四种情况:

数据较多,不截图了,总结如下:

ProductID 查询类型 格式 总逻辑读 CPU 内存 时间 每次编译 缓存大小 查询开销
870(4688行) 即时查询 where P.ProductID =870 1305 20 952 20 56 KB 26%
870(4688行) 即时参数查询 where P.ProductID [email protected] 1305 16 1016 16 56 KB 24%
870(4688行) 存储过程 proc :@ProductID = 870 1305 17 928 17 56 KB 26%
870(4688行) 存储过程内声明 proc :where P.ProductID [email protected] 1305 18 984 18 56 KB 24%
897(2行) 即时查询 where P.ProductID =897 20 10 792 10 48 KB 13%
897(2行) 即时参数查询 where P.ProductID [email protected] 1305 17 1016 17 56 KB 37%
897(2行) 存储过程 proc :P_Test @ProductID = 897 20 8 760 8 56 KB 13%
897(2行) 存储过程内声明 proc :where P.ProductID [email protected] 1305 18 984 18 56 KB 37%
时间: 2024-08-01 18:30:53

SQL Server 查询优化(测试02)参数嗅探-执行计划选择的相关文章

SQL Server查询优化方法(查询速度慢的原因很多,常见如下几种) .

SQL Server查询优化方法(查询速度慢的原因很多,常见如下几种) 标签: sql server优化数据库服务器 2014-12-31 10:13 11988人阅读 评论(0) 收藏 举报 本文章已收录于: 今天看到一位博友的文章,觉得不错,转载一下,希望对大家有帮助,更多文章,请访问:http://blog.haoitsoft.com 1.没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷) 2.I/O吞吐量小,形成了瓶颈效应. 3.没有创建计算列导致查询不优化. 4.内存

SQL Server 2008 表变量参数(表值参数)用法

表值参数是 SQL Server 2008 中的新参数类型.表值参数是使用用户定义的表类型来声明的.使用表值参数,可以不必创建临时表或许多参数,即可向 Transact-SQL 语句或例程(如存储过程或函数)发送多行数据. 表值参数与 OLE DB 和 ODBC 中的参数数组类似,但具有更高的灵活性,且与 Transact-SQL 的集成更紧密.表值参数的另一个优势是能够参与基于数据集的操作. (注意:Transact-SQL 通过引用向例程传递表值参数,以避免创建输入数据的副本.) 在 Tra

SQL server 维护计划中 “清除维护任务” 执行报错

SQL server 维护计划中 “清除维护任务” 执行报错,错误如下: 执行查询“EXECUTE master.dbo.xp_delete_file 0,N'',N'',N'2019...”失败,错误如下:“执行扩展存储过程时出错: 参数无效”.失败的原因可能有: 查询本身有问题.未正确设置 "ResultSet" 属性.未正确设置参数或未正确建立连接. 解决方法:检查路径是否为空 原文地址:https://www.cnblogs.com/YokyFitting/p/10936924

Oracle删除一条SQL在Shared Pool里缓存的执行计划的三种方法

在Oracle里第一次执行一条SQL语句后,该SQL语句会被硬解析,而且执行计划和解析树会被缓存到Shared Pool里.方便以后再次执行这条SQL语句时不需要再做硬解析,方便应用系统的扩展.但是如果该SQL对应的表数据量突变或其他原因,Shared Pool里缓存的执行计划和解析树已经不再适用于现在的情况,SQL执行效率急速下降,这种情况下就需要把该SQL缓存在Shared Pool里的执行计划和解析树清理出去,以便对该SQL重新做硬解析,生成新的执行计划和解析树. 从Shared Pool

SQL Server查询优化方法参考(转)

今天看到一位博友的文章,觉得不错,转载一下,希望对大家有帮助,更多文章,请访问:http://blog.haoitsoft.com 1.没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷) 2.I/O吞吐量小,形成了瓶颈效应. 3.没有创建计算列导致查询不优化. 4.内存不足 5.网络速度慢 6.查询出的数据量过大(可以采用多次查询,其他的方法降低数据量) 7.锁或者死锁(这也是查询慢最常见的问题,是程序设计的缺陷) 8.sp_lock,sp_who,活动的用户查看,原因是读写竞

SQL Server查询优化方法

查询速度慢的原因很多,常见如下几种 1.没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷) 2.I/O吞吐量小,形成了瓶颈效应 3.没有创建计算列导致查询不优化 4.内存不足 5.网络速度慢 6.查询出的数据量过大(可以采用多次查询,其他的方法降低数据量) 7.锁或者死锁(这也是查询慢最常见的问题,是程序设计的缺陷) 8.sp_lock,sp_who,活动的用户查看,原因是读写竞争资源. 9.返回了不必要的行和列 10.查询语句不好,没有优化 可以通过如下方法来优化查询 1.把

深入浅出的 SQL Server 查询优化

目前网络数据库的应用已经成为最为广泛的应用之一了,并且关于数据库的安全性,性能都是企业最为关心的事情.数据库渐渐成为企业的命脉,优化查询就解决了每个关于数据库应用的性能问题,在这里microsoft sql server又为我们做了些什么,我们一起关注. 优化查询我们理解起来貌似很抽象,概括范围十分的大,关于数据库这里的优化查询其实很简单,做一个简单的例子.一个数据包经过无数的路由器达到自己的目的地址,如果在经过每个路由器的时候,路由器都会给他指向最近的路,那么他抵达的速度也就是最快的.在数据库

【sql server inject】使用动态查询执行sql语句实例

应某少年要求授权测试一个存在报错注入点的站点,可读取数据库名,但是sqlmap执行–os-shell选项就会莫名当掉: 分步骤测试了几次,发现xp_cmdshell是开启状态,但用sqlmap注入却无法利用XP_cmdshell执行命令? 正好最近在读[SQL注入攻击与防御],感觉这真是一个值得实践的好目标! 为了简化测试步骤,所以文章分为5次进行记录: 0x1 sqlmap常用语句测试 测试1目的:执行cmd命令 测试1结果,测试初期无法连接: ------------------------

sql server service broker中调用存储过程执行跨库操作,不管怎么设置都一直提示 服务器主体 "sa" 无法在当前安全上下文下访问数据库 "dbname"。

用sql server自带的消息队列service borker,调用存储过程中,执行了一个跨库的操作,先是用了一个用户,权限什么都给够了,但是一直提示 服务器主体 "user" 无法在当前安全上下文下访问数据库 "dbname". 想着是架构方面的问题,换sa还是不行.查到微软的一篇文章 提示需要开数据库的 ALTER DATABASE current_db SET TRUSTWORTHY ON 我把跨的那个库设置了还是不行.最后自己写测试代码,代码如下: cre