使用日期表达式查询运行缓慢,但使用字符串文字快速查询

时间:2013-08-14 20:46:20

标签: sql sql-server-2008

我在SQL Server 2008中运行具有以下条件的查询。

Where FK.DT = CAST(DATEADD(m, DATEDIFF(m, 0, getdate()), 0) as DATE)  

查询需要永远以上述条件运行,但如果只是说

Where FK.DT = '2013-05-01' 
它在2分钟内运行良好。 FK.DT键仅包含该月份的起始数据的值。

任何帮助,我都不知道为什么会发生这种情况。

2 个答案:

答案 0 :(得分:23)

这可能会更好:

Where FK.DT = cast(getdate() + 1 - datepart(day, getdate()) as date)

除非您使用跟踪标志4199运行,否则a bug会影响基数估算值。在撰写本文时

SELECT DATEADD(m, DATEDIFF(m, getdate(), 0), 0), 
       DATEADD(m, DATEDIFF(m, 0, getdate()), 0)

返回

+-------------------------+-------------------------+
| 1786-06-01 00:00:00.000 | 2013-08-01 00:00:00.000 |
+-------------------------+-------------------------+

问题在于,在推导基数估计值时,问题中的谓词使用第一个日期而不是第二个日期。所以对于以下设置。

CREATE TABLE FK
(
ID INT IDENTITY PRIMARY KEY,
DT DATE,
Filler CHAR(1000) NULL,
UNIQUE (DT,ID)
)

INSERT INTO FK (DT)
SELECT TOP (1000000) DATEADD(m, DATEDIFF(m, getdate(), 0), 0)
FROM master..spt_values o1, master..spt_values o2
UNION ALL
SELECT               DATEADD(m, DATEDIFF(m, 0, getdate()), 0)

查询1

SELECT COUNT(Filler)
FROM FK
WHERE FK.DT = CAST(DATEADD(m, DATEDIFF(m, 0, getdate()), 0) AS DATE)  

Plan 1

估计匹配行数为100,000。这是与日期'1786-06-01'匹配的数字。

但是以下两个查询

SELECT COUNT(Filler)
FROM FK
WHERE FK.DT = CAST(GETDATE() + 1 - DATEPART(DAY, GETDATE()) AS DATE)

SELECT COUNT(Filler)
FROM FK
WHERE FK.DT = CAST(DATEADD(m, DATEDIFF(m, 0, getdate()), 0) AS DATE)  
OPTION (QUERYTRACEON 4199)

提供此计划

Plan 2

由于更准确的基数估计,该计划现在只进行单个索引搜索而不是完整扫描。

答案 1 :(得分:13)

在大多数情况下,以下内容可能适用。在此特定的情况下,这是一个涉及DATEDIFF的优化程序错误。详细信息herehere。很抱歉怀疑t-clausen.dk,但他的答案根本不是一个直观而合理的解决方案而且不知道错误的存在。

假设DT实际上是DATE而不是像VARCHAR那样愚蠢或者更糟糕的是 - NVARCHAR - 这可能是因为你有一个缓存的计划使用了首次执行时的日期值非常不同,因此选择了适合不同典型数据分布的计划。你有办法克服这个问题:

  1. 通过添加OPTION (RECOMPILE)强制重新编译计划。您可能只需要执行一次,但是您获得的计划可能不适合其他参数。将选项一直留在那里的缺点是,每次查询运行时都要支付编译成本。在很多情况下,这并不重要,我经常选择支付一个已知的小成本,而不是有时候查询运行得稍微快一点,而其他时候运行得非常慢。

    ...
    WHERE FK.DT = CAST(... AS DATE) OPTION (RECOMPILE);
    
  2. 首先使用变量(此处不需要明确CONVERTDATE,请使用MONTH代替m之类的速记 - 这种习惯可以如果您没有记住所有缩写的内容,例如我打赌yw不会产生您期望的结果,那么会导致真正有趣的行为:

    DECLARE @dt DATE = DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0);
    
    ...
    WHERE FK.DT = @dt;
    

    然而,在这种情况下可能会发生同样的事情 - 参数嗅探可能强制使用次优计划来代表不同数据倾斜的不同参数。

  3. 您还可以尝试OPTION (OPTIMIZE FOR (@dt = '2013-08-01')),这会强制SQL Server考虑此值而不是用于编译缓存计划的值,但这需要硬编码的字符串文字,这只会帮助你在8月的剩余时间,此时你需要更新价值。您还可以考虑OPTION (OPTIMIZE FOR UNKNOWN)