Запрос быстрее с атрибутом top
Почему этот запрос быстрее в SQL Server 2008 R2 (версия 10.50.2806.0)
SELECT
MAX(AtDate1),
MIN(AtDate2)
FROM
(
SELECT TOP 1000000000000
at.Date1 AS AtDate1,
at.Date2 AS AtDate2
FROM
dbo.tab1 a
INNER JOIN
dbo.tab2 at
ON
a.id = at.RootId
AND CAST(GETDATE() AS DATE) BETWEEN at.Date1 AND at.Date2
WHERE
a.Number = 223889
)B
затем
SELECT
MAX(AtDate1),
MIN(AtDate2)
FROM
(
SELECT
at.Date1 AS AtDate1,
at.Date2 AS AtDate2
FROM
dbo.tab1 a
INNER JOIN
dbo.tab2 at
ON
a.id = at.RootId
AND CAST(GETDATE() AS DATE) BETWEEN at.Date1 AND at.Date2
WHERE
a.Number = 223889
)B
?
Второе утверждение с TOP
Атрибут в шесть раз быстрее.
count(*)
из внутреннего подзапроса 9280 строк.
Могу ли я использовать подсказку, чтобы объявить, что оптимизатор SQL Server сделает это правильно?
2 ответа
Я вижу, вы сейчас опубликовали планы. Просто удача в розыгрыше.
Ваш фактический запрос - соединение из 16 таблиц.
SELECT max(atDate1) AS AtDate1,
min(atDate2) AS AtDate2,
max(vtDate1) AS vtDate1,
min(vtDate2) AS vtDate2,
max(bgtDate1) AS bgtDate1,
min(bgtDate2) AS bgtDate2,
max(lftDate1) AS lftDate1,
min(lftDate2) AS lftDate2,
max(lgtDate1) AS lgtDate1,
min(lgtDate2) AS lgtDate2,
max(bltDate1) AS bltDate1,
min(bltDate2) AS bltDate2
FROM (SELECT TOP 100000 at.Date1 AS atDate1,
at.Date2 AS atDate2,
vt.Date1 AS vtDate1,
vt.Date2 AS vtDate2,
bgt.Date1 AS bgtDate1,
bgt.Date2 AS bgtDate2,
lft.Date1 AS lftDate1,
lft.Date2 AS lftDate2,
lgt.Date1 AS lgtDate1,
lgt.Date2 AS lgtDate2,
blt.Date1 AS bltDate1,
blt.Date2 AS bltDate2
FROM dbo.Tab1 a
INNER JOIN dbo.Tab2 at
ON a.id = at.Tab1Id
AND cast(Getdate() AS DATE) BETWEEN at.Date1 AND at.Date2
INNER JOIN dbo.Tab5 v
ON v.Tab1Id = a.Id
INNER JOIN dbo.Tab16 g
ON g.Tab5Id = v.Id
INNER JOIN dbo.Tab3 vt
ON v.id = vt.Tab5Id
AND cast(Getdate() AS DATE) BETWEEN vt.Date1 AND vt.Date2
LEFT OUTER JOIN dbo.Tab4 vk
ON v.id = vk.Tab5Id
LEFT OUTER JOIN dbo.VerkaufsTab3 vkt
ON vk.id = vkt.Tab4Id
LEFT OUTER JOIN dbo.Plu p
ON p.Tab4Id = vk.Id
LEFT OUTER JOIN dbo.Tab15 bg
ON bg.Tab5Id = v.Id
LEFT OUTER JOIN dbo.Tab7 bgt
ON bgt.Tab15Id = bg.Id
AND cast(Getdate() AS DATE) BETWEEN bgt.Date1 AND bgt.Date2
LEFT OUTER JOIN dbo.Tab11 b
ON b.Tab15Id = bg.Id
LEFT OUTER JOIN dbo.Tab14 lf
ON lf.Id = b.Id
LEFT OUTER JOIN dbo.Tab8 lft
ON lft.Tab14Id = lf.Id
AND cast(Getdate() AS DATE) BETWEEN lft.Date1 AND lft.Date2
LEFT OUTER JOIN dbo.Tab13 lg
ON lg.Id = b.Id
LEFT OUTER JOIN dbo.Tab9 lgt
ON lgt.Tab13Id = lg.Id
AND cast(Getdate() AS DATE) BETWEEN lgt.Date1 AND lgt.Date2
LEFT OUTER JOIN dbo.Tab10 bl
ON bl.Tab11Id = b.Id
LEFT OUTER JOIN dbo.Tab6 blt
ON blt.Tab10Id = bl.Id
AND cast(Getdate() AS DATE) BETWEEN blt.Date1 AND blt.Date2
WHERE a.Nummer = 223889) B
Как на хороших, так и на плохих планах План выполнения показывает "Причину досрочного прекращения оптимизации выписки" как "Тайм-аут".
Два плана имеют немного разные порядки соединения.
Единственное объединение в планах, не удовлетворенных поиском индекса, заключается в том, что Tab9
, Это имеет 63 926 строк.
Отсутствующие детали индекса в плане выполнения предполагают, что вы создадите следующий индекс.
CREATE NONCLUSTERED INDEX [miising_index]
ON [dbo].[Tab9] ([Date1],[Date2])
INCLUDE ([Tab13Id])
Проблемная часть плохого плана хорошо видна в SQL Sentry Plan Explorer.
По оценкам SQL Server, 1.349174 строки будут возвращены из предыдущих объединений, входящих в объединение Tab9
, И поэтому стоит объединить вложенные циклы, как если бы ему нужно было выполнить сканирование внутренней таблицы 1.349174 раза.
Фактически в это объединение вводятся 2600 строк, что означает, что он выполняет 2600 полных сканирований Tab9
(2600 * 63,926 = 164,569,600 строк.)
Просто так получилось, что при хорошем плане предполагаемое количество строк, входящих в объединение, составляет 2,74319. Это по-прежнему неверно на три порядка, но немного увеличенная оценка означает, что вместо этого SQL Server предпочитает хеш-соединение. Хеш-соединение просто проходит через Tab9
Я бы сначала попытался добавить отсутствующий индекс на Tab9
,
Также / вместо этого вы можете попробовать обновить статистику по всем задействованным таблицам (особенно с предикатом даты, таким как Tab2
Tab3
Tab7
Tab8
Tab6
) и посмотрите, поможет ли это исправить огромное расхождение между оценочными и фактическими строками слева от плана.
Также может помочь разбиение запроса на более мелкие части и их материализация во временные таблицы с соответствующими индексами. Затем SQL Server может использовать статистику по этим частичным результатам, чтобы принимать лучшие решения для объединений на более поздних этапах плана.
Только в качестве крайней меры я мог бы использовать подсказки запросов, чтобы попытаться форсировать план с помощью хеш-соединения. Ваши варианты для этого либо USE PLAN
подсказка, в этом случае вы диктуете именно тот план, который вам нужен, включая все типы и заказы соединения, или указав LEFT OUTER HASH JOIN tab9 ...
, Этот второй вариант также имеет побочный эффект фиксации всех порядков соединения в плане. И то, и другое означает, что SQL Server будет строго ограничен - это его способность корректировать план с учетом изменений в распределении данных.
Трудно ответить, не зная размера и структуры ваших таблиц и не имея возможности увидеть весь план выполнения. Но различие в обоих планах заключается в соединении с хэш-соответствием для запроса "top n" против соединения с вложенным циклом для другого. Hash Match является очень ресурсоемким объединением, потому что сервер должен подготовить хэш-блоки для его использования. Но это становится намного более эффективным для больших таблиц, в то время как Nested Loops, сравнивая каждую строку в одной таблице с каждой строкой в другой таблице, отлично подходит для небольших таблиц, потому что такая подготовка не требуется. Я думаю, что, выбрав TOP 1000000000000 строк в подзапросе, вы дадите оптимизатору подсказку, что подзапрос выдаст большое количество данных, поэтому он использует Hash Match. Но на самом деле вывод небольшой, поэтому Nested Loops работает лучше. То, что я только что сказал, основано на клочках информации, поэтому, пожалуйста, сердечно критикуйте мой ответ;).