我有一个简单的日期表(日期,日期ID),其中包含1900年1月1日至2100年12月31日之间的日期列表。
使用between
运算符和硬编码参数值从表中选择时,我得到一个正确的查询计划,其中包含3个估计行,而不是2个实际行:
select v.Date from Dates v
where v.Date between '20130128' and '20130129';
但是,当使用参数替换硬编码值时,查询计划会更改为非常糟糕的计划,估计行数超过6000,实际行数仅为2行:
select v.Date from Dates v
where v.Date between @startdate and @enddate;
查询计划本身是相同的,只是估计行的差异导致参数化查询比硬编码查询慢大约4倍。有什么我不知道为什么参数化版本运行得这么慢,我可以给SQL Server哪些索引/提示来帮助它使用正确的查询计划?
其他一些信息:
=
条件时不会出现此问题,它似乎特定于between
运算符。option(recompile)
,我会得到一个完美的查询计划,与硬编码查询相同。我知道实现 怀疑,这是某种参数嗅探问题。为什么不在查询中添加option(recompile)
?这被用作更大的复杂查询的一部分,我理解良好的做法是让SQL Server尽可能地从缓存中重用查询计划。
编辑和更新:感谢迄今为止的深思熟虑的回应。为了进一步细化问题,查询计划为上述两个查询使用了一个非常好的索引,但为什么它不能识别参数化查询的日期范围只有两天宽,为什么它认为范围是6000行宽?特别是当查看查询计划时,SQL Server正在为硬编码查询执行自动参数化?在基础查询计划中,两个计划看起来都相同,因为它们都是参数化的!
答案 0 :(得分:3)
查询计划基于第一次运行查询时的参数值。这称为parameter sniffing。添加option (recompile)
时,会为每次执行生成新计划。
根据SQL查询的哈希值缓存查询计划。因此,对于您的查询的两个版本都有不同的缓存插槽。
添加option (recompile)
是一个很好的解决方案。你也可以使用:
option (optimize for (@startdate = '20130128', @enddate = '20130129'));
生成查询计划,就像传入了这些值一样。
要进行测试,您可以使用以下命令从缓存中删除所有计划:
DBCC FREEPROCCACHE
答案 1 :(得分:2)
问题可能是parameter sniffing引起的,OPTIMIZE FOR UNKNOWN
是一种根据您第一次传递的参数优化查询计划的技术。
我过去使用参数嗅探日期参数的经验很糟糕 - 参数嗅探显然有可能最初为查询输入的值不代表查询的典型用法(导致查询计划)针对非典型值进行了优化),但特别是对于日期参数,我经常发现生成的查询计划对于提供的所有值实际上效率非常低。我不知道为什么这是禁用参数嗅探通常修复它。
您可以使用{{3}}阻止SQL Server进行参数嗅探(我从未使用过这个,所以不能保证它有效),或者将参数复制到局部变量似乎会阻止SQL Server能够执行参数嗅探
-- Perform the query using @StartDateLocal and @EndDateLocal
DECLARE @StartDateLocal DATETIME;
SET @StartDateLocal = @StartDate;
以这种方式禁用参数嗅探比每次强制重新编译查询计划要好,因为编译查询计划与执行查询的成本相比通常相对昂贵,除非查询相当慢。