16

我在 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键包含仅当月开始数据的值。

任何帮助,我只是不知道为什么会这样。

4

2 回答 2

25

这可能会更好:

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

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

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)  

计划一

估计匹配的行数将是 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)

给出这个计划

计划 2

由于基数估计要准确得多,因此该计划现在只进行一次索引查找而不是完整扫描。

于 2013-08-14T21:13:11.267 回答
13

在大多数情况下,以下可能适用。在这种特定情况下,这是一个涉及DATEDIFF. 此处此处的详细信息。很抱歉怀疑 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)

于 2013-08-14T20:55:49.147 回答