Почему мой ORDER BY сортирует две таблицы до ИСКЛЮЧЕНИЯ (медленно), а не после (быстро)?

12

Головоломка оптимизатора запросов SQL Server 2008 R2

У нас есть две таблицы, каждая из которых содержит 9 миллионов строк. 70 000 строк разные, остальные одинаковые.

Это быстро, 13 секунд,

select * from bigtable1
except select * from similar_bigtable2

Это сортирует вывод и также быстро, 13 секунд,

select * into #q from bigtable1
except select * from similar_bigtable2
select * from #q order by sort_column

Пока это невероятно медленно

;with q as (
    select * from bigtable1
    except select * from similar_bigtable2
)
select * from q order by sort_column

И даже «трюк», который я иногда использую, чтобы намекнуть SQL Server, что ему необходимо предварительно рассчитать определенную часть запроса, прежде чем он будет выполнен, не работает и также приводит к медленному запросу:

;with q as (
    select top 100 percent * from bigtable1
    except select * from similar_bigtable2
)
select * from q order by sort_column

Глядя на планы запросов, найти причину несложно:

План запроса План запроса с ORDER BY

SQL Server помещает два сорта по 9 миллионов строк перед хэш-соответствием, в то время как я бы предпочел, чтобы после хеш-соответствия было добавлено только один вид из 70 000 строк .

Итак, вопрос: как я могу поручить оптимизатору запросов сделать это?

thomaspaulb
источник
3
Он не сортирует перед хэш-соответствием, он сортирует, а затем выполняет соединение слиянием (а не хеш-соединение). Может быть, есть подсказка для принудительного хеш-соединения (или предотвращения слияния)?
Тило
3
Похоже, что оптимизатор запросов SQL Server определил, что сортировка данных была полезной, поэтому он мог использовать гораздо более быстрое объединение слиянием (которое работает только для отсортированных данных) вместо гораздо более медленного соединения с совпадением по хэшу или соединения с вложенным циклом ....
marc_s
9
Вы пробовали альтернативы EXCEPT(например OUTER JOIN)? Я понимаю, что синтаксис менее удобен, но вы можете лучше использовать подсказки индексации / объединения (или вам это может не понадобиться). Альтернатива, которую вы используете сейчас (сначала добавьте в таблицу #temp), является последним средством, но в некоторых случаях это единственный способ заставить оптимизатор полностью разделить две части запроса так, как вам нужно.
Аарон Бертран

Ответы:

1

Основное различие между этими двумя планами запросов заключается в разнице между Hash Match и Merge Join. Hash Match более эффективен, и, как вы можете видеть, запрос выполняется быстрее в варианте 1 (без использования CTE).

CTE - отличный инструмент, но он кажется неэффективным в двух случаях: сложные предикаты или неуникальный родительский / дочерний ключ. В вашем случае нет уникального ключа, и SQL-сервер должен сначала отсортировать наборы данных, чтобы выполнить ваше требование. Посмотрите на ссылку ниже, которая расскажет вам больше об этой проблеме: http://blogs.msdn.com/b/sqlcat/archive/2011/04/28/optimize-recursive-cte-query.aspx

Поэтому кажется, что вы должны либо принять его медлительность, либо переписать логику с циклом WHILE, что может быть более эффективным.

Небо
источник
0

Попробуйте это, лучше?

select * from
(
    select * from bigtable1
    except 
    select * from similar_bigtable2
) t
order by sort_column
Гордон Белл
источник
0

Это не идеальное решение, но если вы не можете структурировать tsql для генерации эффективного плана, вы можете установить руководство плана, чтобы заставить план, который вы хотите. Это будет означать, что если станет доступен более эффективный план, SQL не будет его рассматривать, но это вариант.

cfradenburg
источник