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

use*_*642 15 sql sql-server-2008

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

Where FK.DT = CAST(DATEADD(m, DATEDIFF(m, 0, getdate()), 0) as DATE)  
Run Code Online (Sandbox Code Playgroud)

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

Where FK.DT = '2013-05-01' 
Run Code Online (Sandbox Code Playgroud)

它在2分钟内运行良好.FK.DTkey包含仅月份的起始数据的值.

任何帮助,我只是无知为什么会发生这种情况.

t-c*_*.dk 24

这可能更好:

Where FK.DT = cast(getdate() + 1 - datepart(day, getdate()) as date)
Run Code Online (Sandbox Code Playgroud)

除非您使用跟踪标志4199运行,否则会有一个影响基数估计的错误.在撰写本文时

SELECT DATEADD(m, DATEDIFF(m, getdate(), 0), 0), 
       DATEADD(m, DATEDIFF(m, 0, getdate()), 0)
Run Code Online (Sandbox Code Playgroud)

返回

+-------------------------+-------------------------+
| 1786-06-01 00:00:00.000 | 2013-08-01 00:00:00.000 |
+-------------------------+-------------------------+
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

查询1

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

计划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)
Run Code Online (Sandbox Code Playgroud)

给这个计划

计划2

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

  • 很抱歉我怀疑你.更新了我的回答. (7认同)
  • @MikaelEriksson在[dba.se]聊天中指出这是一个[修复bug](http://support.microsoft.com/kb/2481274/sv),但你需要启用跟踪标志4199才能获得修复. (6认同)
  • @ t-clausen.dk - 没问题.看起来像一个有趣的估计bug.我冒昧地将一个演示编辑到你的答案中. (2认同)

Aar*_*and 13

在大多数情况下,以下可能适用.在这种特定情况下,这是一个涉及的优化器错误DATEDIFF.细节在这里这里.很抱歉怀疑t-clausen.dk,但他的答案根本不是一个直观而合理的解决方案而不知道错误的存在.

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

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

    ...
    WHERE FK.DT = CAST(... AS DATE) OPTION (RECOMPILE);
    
    Run Code Online (Sandbox Code Playgroud)
  2. 使用一个变量第一(不需要明确CONVERTDATE这里了,请用MONTH代替速记一样m-这种习惯可能会导致真正的滑稽的行为,如果你没有记住什么都的缩写做,比如我敢打赌yw不产生你期望的结果:

    DECLARE @dt DATE = DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0);
    
    ...
    WHERE FK.DT = @dt;
    
    Run Code Online (Sandbox Code Playgroud)

    然而,在这种情况下可能会发生同样的事情 - 参数嗅探可能会强制将次优计划用于表示不同数据偏斜的不同参数.

  3. 你也可以试验一下OPTION (OPTIMIZE FOR (@dt = '2013-08-01')),这会强迫SQL Server考虑这个值而不是那个用来编译缓存计划的值,但这需要一个硬编码的字符串文字,这只会帮助你在8月剩下的时间,此时您需要更新该值.你也可以考虑一下OPTION (OPTIMIZE FOR UNKNOWN).

  • @ user2158642是的,这就是重点.如果您在原始查询中提前3个月使用过,那么您可能也会"修复"您的问题.它与重写计算日期的方式没有关系,它与更改日期有关 (2认同)