Запрос выполняется медленно с выражением даты, но быстро с строковым литералом

Я запускаю запрос с условием ниже в 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 содержит значения только начальных данных месяца.

Любая помощь, я просто не знаю, почему это происходит.

Ответ 1

Это может работать лучше:

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)  

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

Из-за гораздо более точных оценок мощности план теперь просто выполняет поиск только одного индекса, а не полное сканирование.

Ответ 2

В большинстве случаев, возможно, применяется ниже. В этом конкретном случае это ошибка оптимизатора с участием DATEDIFF. Подробности здесь и . Извините за сомнение t-clausen.dk, но его ответ просто не был интуитивным и логичным решением, не зная о существовании ошибки.

Итак, если DT на самом деле DATE, а не что-то глупое как VARCHAR или - еще хуже - NVARCHAR - это, вероятно, потому, что у вас есть план кеширования, который использовал очень другое значение даты при первом запуске, поэтому выбрал план, обслуживающий совсем другое типичное распределение данных. Есть способы преодолеть это:

  • Заставьте перекомпилировать план, добавив OPTION (RECOMPILE). Возможно, вам придется сделать это один раз, но тогда ваш план может оказаться не оптимальным для других параметров. Недостатком того, чтобы оставить параметр там все время, является то, что вы затем оплачиваете стоимость компиляции каждый раз, когда выполняется запрос. Во многих случаях это не является существенным, и я часто предпочитаю оплачивать известную небольшую стоимость, а не иногда иметь запрос, который выполняется немного быстрее, а в других случаях он работает очень медленно.

    ...
    WHERE FK.DT = CAST(... AS DATE) OPTION (RECOMPILE);
    
  • Сначала используйте переменную (нет необходимости в явном CONVERT to DATE и используйте MONTH вместо сокращенного типа m - эта привычка может привести к действительно смешному поведению, если вы 't запомнил, что делают все аббревиатуры, например, ставка y и w не дает ожидаемых результатов):

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

    Однако в этом случае может произойти одно и то же: параметр sniffing может принудить субоптимальный план, который будет использоваться для разных параметров, представляющих разные искажения данных.

  • Вы также можете поэкспериментировать с OPTION (OPTIMIZE FOR (@dt = '2013-08-01')), который заставил бы SQL Server рассматривать это значение вместо того, которое было использовано для компиляции кэшированного плана, но для этого потребуется стробированный строковый литерал, который будет только помогите вам до конца августа, после чего вам нужно будет обновить значение. Вы также можете рассмотреть OPTION (OPTIMIZE FOR UNKNOWN).