“Between”运算符在使用参数时生成错误的查询计划

时间:2013-02-13 10:24:44

标签: sql sql-server sql-server-2008-r2

我有一个简单的日期表(日期,日期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),我会得到一个完美的查询计划,与硬编码查询相同。
  • 日期表只有两列,Date和DateID,主键DateID列上有聚簇索引,Date列上有唯一的非聚簇索引。所有人都有更新的统计数据。
  • 查询计划为硬编码查询执行自动参数化,用@ 1和@2替换硬编码值,并将查询显示为大写。它似乎不会对参数化查询执行任何转换。
  • 使用SQL Server 2008 R2。

我知道实现 怀疑,这是某种参数嗅探问题。为什么不在查询中添加option(recompile)?这被用作更大的复杂查询的一部分,我理解良好的做法是让SQL Server尽可能地从缓存中重用查询计划。

编辑和更新:感谢迄今为止的深思熟虑的回应。为了进一步细化问题,查询计划为上述两个查询使用了一个非常好的索引,但为什么它不能识别参数化查询的日期范围只有两天宽,为什么它认为范围是6000行宽?特别是当查看查询计划时,SQL Server正在为硬编码查询执行自动参数化?在基础查询计划中,两个计划看起来都相同,因为它们都是参数化的!

2 个答案:

答案 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;

以这种方式禁用参数嗅探比每次强制重新编译查询计划要好,因为编译查询计划与执行查询的成本相比通常相对昂贵,除非查询相当慢。