Запрос быстрее с атрибутом 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,

Также / вместо этого вы можете попробовать обновить статистику по всем задействованным таблицам (особенно с предикатом даты, таким как Tab2Tab3Tab7Tab8Tab6) и посмотрите, поможет ли это исправить огромное расхождение между оценочными и фактическими строками слева от плана.

Также может помочь разбиение запроса на более мелкие части и их материализация во временные таблицы с соответствующими индексами. Затем SQL Server может использовать статистику по этим частичным результатам, чтобы принимать лучшие решения для объединений на более поздних этапах плана.

Только в качестве крайней меры я мог бы использовать подсказки запросов, чтобы попытаться форсировать план с помощью хеш-соединения. Ваши варианты для этого либо USE PLAN подсказка, в этом случае вы диктуете именно тот план, который вам нужен, включая все типы и заказы соединения, или указав LEFT OUTER HASH JOIN tab9 ..., Этот второй вариант также имеет побочный эффект фиксации всех порядков соединения в плане. И то, и другое означает, что SQL Server будет строго ограничен - это его способность корректировать план с учетом изменений в распределении данных.

Трудно ответить, не зная размера и структуры ваших таблиц и не имея возможности увидеть весь план выполнения. Но различие в обоих планах заключается в соединении с хэш-соответствием для запроса "top n" против соединения с вложенным циклом для другого. Hash Match является очень ресурсоемким объединением, потому что сервер должен подготовить хэш-блоки для его использования. Но это становится намного более эффективным для больших таблиц, в то время как Nested Loops, сравнивая каждую строку в одной таблице с каждой строкой в ​​другой таблице, отлично подходит для небольших таблиц, потому что такая подготовка не требуется. Я думаю, что, выбрав TOP 1000000000000 строк в подзапросе, вы дадите оптимизатору подсказку, что подзапрос выдаст большое количество данных, поэтому он использует Hash Match. Но на самом деле вывод небольшой, поэтому Nested Loops работает лучше. То, что я только что сказал, основано на клочках информации, поэтому, пожалуйста, сердечно критикуйте мой ответ;).

Другие вопросы по тегам