В общем, это сложная проблема, но есть несколько вещей, которые мы можем сделать, чтобы помочь оптимизатору выбрать план. Этот скрипт создает таблицу с 10000 строк с известным псевдослучайным распределением строк для иллюстрации:
CREATE TABLE dbo.SomeDateTable
(
Id INTEGER IDENTITY(1, 1) PRIMARY KEY NOT NULL,
StartDate DATETIME NOT NULL,
EndDate DATETIME NOT NULL
);
GO
SET STATISTICS XML OFF
SET NOCOUNT ON;
DECLARE
@i INTEGER = 1,
@s FLOAT = RAND(20120104),
@e FLOAT = RAND();
WHILE @i <= 10000
BEGIN
INSERT dbo.SomeDateTable
(
StartDate,
EndDate
)
VALUES
(
DATEADD(DAY, @s * 365, {d '2009-01-01'}),
DATEADD(DAY, @s * 365 + @e * 14, {d '2009-01-01'})
)
SELECT
@s = RAND(),
@e = RAND(),
@i += 1
END
Первый вопрос - как проиндексировать эту таблицу. Одним из вариантов является предоставление двух индексов для DATETIME
столбцов, чтобы оптимизатор мог, по крайней мере, выбрать, выполнять поиск StartDate
или нет EndDate
.
CREATE INDEX nc1 ON dbo.SomeDateTable (StartDate, EndDate)
CREATE INDEX nc2 ON dbo.SomeDateTable (EndDate, StartDate)
Естественно, неравенства , как StartDate
и EndDate
означают , что только один столбец в каждом индексе может поддерживать искать в примере запроса, но это про лучшее , что мы можем сделать. Мы могли бы рассмотреть возможность сделать второй столбец в каждом индексе, INCLUDE
а не ключом, но у нас могут быть другие запросы, которые могут выполнять поиск равенства в первом столбце и поиск неравенства во втором столбце. Кроме того, мы можем получить лучшую статистику таким образом. Тем не мение...
DECLARE
@StartDateBegin DATETIME = {d '2009-08-01'},
@StartDateEnd DATETIME = {d '2009-10-15'},
@EndDateBegin DATETIME = {d '2009-08-05'},
@EndDateEnd DATETIME = {d '2009-10-22'}
SELECT
COUNT_BIG(*)
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.StartDate BETWEEN @StartDateBegin AND @StartDateEnd
AND sdt.EndDate BETWEEN @EndDateBegin AND @EndDateEnd
В этом запросе используются переменные, поэтому в общем случае оптимизатор будет угадывать селективность и распределение, в результате чего предполагаемая оценка мощности составит 81 строка . Фактически, запрос выдает 2076 строк, что может оказаться важным в более сложном примере.
В SQL Server 2008 с пакетом обновления 1 (SP1) CU5 или более поздней версии (или R2 RTM CU1) мы можем воспользоваться преимуществами оптимизации встраивания параметров, чтобы получить более точные оценки, просто добавив OPTION (RECOMPILE)
в SELECT
запрос выше. Это приводит к компиляции непосредственно перед выполнением пакета, что позволяет SQL Server «видеть» реальные значения параметров и оптимизировать их. С этим изменением оценка улучшается до 468 строк (хотя вам нужно проверить план выполнения, чтобы увидеть это). Эта оценка лучше, чем 81 ряд, но все же не так уж близко. Расширения моделирования, включенные с помощью флага трассировки 2301, могут помочь в некоторых случаях, но не с этим запросом.
Проблема в том, что строки, определенные двумя поисками диапазона, перекрываются. Одно из упрощающих допущений, сделанных в компоненте оценки затрат и мощности оптимизатора, заключается в том, что предикаты являются независимыми (поэтому, если оба имеют селективность 50%, предполагается, что результат применения обоих предикатов квалифицирует 50% из 50% = 25% строк ). Там, где подобная корреляция является проблемой, мы часто можем обойти ее с помощью многоколоночной и / или отфильтрованной статистики. С двумя диапазонами с неизвестными начальной и конечной точками это становится непрактичным. Вот где нам иногда приходится прибегать к переписыванию запроса в форме, которая дает лучшую оценку:
SELECT COUNT(*) FROM
(
SELECT
sdt.Id
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.StartDate BETWEEN @StartDateBegin AND @StartDateEnd
INTERSECT
SELECT
sdt.Id
FROM dbo.SomeDateTable AS sdt
WHERE
sdt.EndDate BETWEEN @EndDateBegin AND @EndDateEnd
) AS intersected (id)
OPTION (RECOMPILE)
Эта форма дает оценку времени выполнения в 2110 строк (против 2076 фактических). Если у вас не включен режим TF 2301, в этом случае более продвинутые методы моделирования позволяют выполнить трюк и получить точно такую же оценку, как и раньше: 468 строк.
Однажды SQL Server может получить встроенную поддержку для интервалов. Если это идет с хорошей статистической поддержкой, разработчики могут бояться настраивать планы запросов, подобные этому, чуть меньше.