4

我有一个简单的日期表 (Date, DateID),其中包含 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 无论如何都在为硬编码查询执行自动参数化?在底层查询计划中,两个计划看起来相同,因为它们都是参数化的!

4

2 回答 2

3

查询计划基于您首次运行查询时的参数值。这称为参数嗅探。添加时option (recompile),每次执行都会生成一个新计划。

查询计划基于 SQL 查询的哈希值进行缓存。因此,两个版本的查询都有不同的缓存槽。

添加option (recompile)是一个很好的解决方案。您还可以使用:

option (optimize for (@startdate = '20130128', @enddate = '20130129'));

生成查询计划,就好像这些值已被传入一样。

为了进行测试,您可以使用以下命令从缓存中删除所有计划:

DBCC FREEPROCCACHE
于 2013-02-13T10:41:32.250 回答
2

问题可能是由于参数嗅探,一种旨在根据您第一次传递的参数优化查询计划的技术。

过去,我在使用日期参数嗅探参数方面有过不好的经历 - 参数嗅探显然有可能最初为查询输入的值不代表查询的典型使用(导致查询计划针对非-典型值),但是特别是对于日期参数,我经常发现生成的查询计划实际上对于提供的所有值都非常低效。我不知道为什么这会禁用参数嗅探通常会修复它。

您可以通过使用OPTIMIZE FOR UNKNOWN(我从未使用过,因此无法保证它有效)来防止 SQL Server 参数嗅探,或者将参数复制到局部变量中似乎可以防止 SQL Server 能够执行参数嗅探

-- Perform the query using @StartDateLocal and @EndDateLocal
DECLARE @StartDateLocal DATETIME;
SET @StartDateLocal = @StartDate;

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

于 2013-02-13T10:53:01.447 回答