Есть ли разница в производительности между CTE, подзапросом, временной таблицей или переменной таблицы?

222

В этом превосходном вопросе SO различия между CTEи sub-queriesбыли обсуждены.

Я хотел бы специально спросить:

При каких обстоятельствах каждое из следующих действий более эффективно / быстрее?

  • КТР
  • Sub-запросы
  • Временный стол
  • Переменная таблицы

Традиционно я использовал много temp tablesв разработке stored procedures- так как они кажутся более читабельными, чем множество переплетенных подзапросов.

Non-recursive CTEs очень хорошо инкапсулируют наборы данных и очень удобочитаемы, но есть ли конкретные обстоятельства, когда можно сказать, что они всегда будут работать лучше? или дело в том, что приходится постоянно искать разные варианты, чтобы найти наиболее эффективное решение?


РЕДАКТИРОВАТЬ

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

whytheq
источник
4
Общий ответ: это зависит. И это зависит от многих факторов, любое общее утверждение, скорее всего, неверно - в некоторых ситуациях. В основном: вам нужно проверить и измерить - посмотрите, что работает лучше для вас!
marc_s
@marc_s - хорошо; может быть, этот вопрос должен быть закрыт за субъективность? Напоминаю, что многие вопросы SQL по SO могут быть оценены как субъективные.
Whytheq
1
Он может быть закрыт как слишком широкий - и я согласен с вами - многие вещи и темы в SQL действительно получат ответ, зависит от этого . Иногда можно перечислить два или три критерия для принятия решения, но с вашим вопросом здесь практически невозможно дать здравый совет - это зависит от очень многих - структуры ваших таблиц, данные в этих таблицах, запросы, которые вы используете, ваша стратегия индексирования и многое другое ....
marc_s
@marc_s было бы неплохо попытаться сохранить - какой-нибудь совет относительно возможных изменений в OP, чтобы попытаться сделать его более конкретным и узким?
Whytheq
Обратите внимание, что этот вопрос относится к SQL Server. Для других БД, таких как postgres, CTE часто намного медленнее, чем эквивалентные подзапросы (см. Http://blog.2ndquadrant.com/postgresql-ctes-are-optimization-fences/ )
Jay

Ответы:

243

SQL является декларативным языком, а не процедурным языком. То есть вы создаете оператор SQL для описания желаемых результатов. Вы не говорите движку SQL, как выполнять работу.

Как правило, желательно, чтобы механизм SQL и оптимизатор SQL нашли лучший план запроса. На разработку движка SQL уходит много человеко-лет, поэтому позвольте инженерам делать то, что они умеют.

Конечно, бывают ситуации, когда план запроса не является оптимальным. Затем вы хотите использовать подсказки запроса, реструктурировать запрос, обновлять статистику, использовать временные таблицы, добавлять индексы и т. Д., Чтобы повысить производительность.

Что касается вашего вопроса. Теоретически производительность CTE и подзапросов должна быть одинаковой, поскольку оба предоставляют одинаковую информацию оптимизатору запросов. Одно из отличий состоит в том, что CTE, использованный более одного раза, может быть легко идентифицирован и рассчитан один раз. Результаты могут быть сохранены и прочитаны несколько раз. К сожалению, SQL Server, похоже, не использует преимущества этого базового метода оптимизации (вы можете назвать это общим устранением подзапроса).

Временные таблицы - это другое дело, потому что вы предоставляете больше рекомендаций о том, как должен выполняться запрос. Одно из основных отличий заключается в том, что оптимизатор может использовать статистику из временной таблицы для составления плана запроса. Это может привести к повышению производительности. Кроме того, если у вас есть сложный CTE (подзапрос), который используется более одного раза, то сохранение его во временной таблице часто дает повышение производительности. Запрос выполняется только один раз.

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

Гордон Линофф
источник
11
Некоторые исследования Microsoft о возможных будущих усовершенствованиях в этой области содержатся в публикации «Эффективное использование похожих выражений для обработки запросов», доступной здесь
Martin Smith,
3
Учитывая, что этот документ был представлен в 2007 году, есть ли идея включить его в SQL Server 2012?
Гордон Линофф
3
Отличный ответ! Просто чтобы подчеркнуть: SQL является декларативным языком, и мы не контролируем, КАК данные извлекаются. Поэтому производительность / скорость варьируется от запроса к запросу.
Симха Хабинский
2
@RGS. , , Индексы во временных таблицах определенно улучшают запросы, которые могут использовать эти индексы - как в случае индексов для постоянной таблицы. Но если вы материализуете подзапрос как временную таблицу, вы можете потерять преимущество индексов в исходных таблицах.
Гордон Линофф
2
@RGS. , Когда механизм базы данных материализует подзапрос / CTE в ходе выполнения сложного запроса, он не добавляет индексы для материализации. Вы можете сделать это вручную, используя временные таблицы.
Гордон Линофф
77

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

Конечно, есть случаи, когда вы можете распутать CTE или удалить подзапросы и заменить их таблицей #temp и сократить продолжительность. Это может быть связано с различными вещами, такими как устаревшая статистика, невозможность даже получить точную статистику (например, присоединение к табличной функции), параллелизм или даже неспособность сгенерировать оптимальный план из-за сложности запроса ( в этом случае его разбиение может дать оптимизатору шанс на победу). Но есть также случаи, когда ввод / вывод, связанный с созданием таблицы #temp, может перевесить другие аспекты производительности, которые могут сделать конкретную форму плана с использованием CTE менее привлекательной.

Честно говоря, существует слишком много переменных, чтобы дать «правильный» ответ на ваш вопрос. Не существует предсказуемого способа узнать, когда запрос может дать предпочтение тому или иному подходу - просто знайте, что теоретически одна и та же семантика для CTE или отдельного подзапроса должна выполняться точно так же. Я думаю, что ваш вопрос будет более ценным, если вы представите некоторые случаи, когда это не так - возможно, вы обнаружили ограничение в оптимизаторе (или обнаружили известный), или что ваши запросы не являются семантически эквивалентными или этот содержит элемент, который мешает оптимизации.

Поэтому я бы предложил написать запрос так, чтобы он казался вам наиболее естественным и отклонялся бы только тогда, когда вы обнаружите реальную проблему производительности, с которой сталкивается оптимизатор. Лично я оцениваю их как CTE, а затем подзапрос, где таблица #temp является последним средством.

Аарон Бертран
источник
4
+1 оказался довольно субъективным вопросом; Я надеюсь, что это не закрыто для того, чтобы быть слишком расплывчатым, поскольку ответы до сих пор информативны. Я понимаю :-) вам не нравится, когда вопросы меняются, но есть ли у вас какие-либо предложения по сужению вопроса в ОП?
Whytheq
2
Я думаю, что этот вопрос в порядке, вы заметите, что пока нет единого голоса для закрытия, но если ответы начнут бродить вокруг, это, вероятно, будет закрыто. Как я предположил в своем ответе, если у вас есть конкретный случай, когда вы видите большую разницу между CTE и подзапросом, начните новый вопрос с реальных запросов и планов выполнения (и он может лучше подходить для dba.se ) , Просто поймите, что ответ на этот запрос может не совпадать с ответом на другой запрос с тем же сценарием.
Аарон Бертран
Прямо под вашим вопросом есть ссылки link / edit / close / flag- если были какие-либо голоса, чтобы закрыть вопрос, вы увидите, close (n)где nуказано количество пользователей, которые проголосовали за закрытие вашего вопроса. Если вы нажмете на ссылку, вы увидите причины, выбранные этими пользователями.
Аарон Бертран
@whytheq также см. это недавнее сообщение в блоге Боба Бошемина . Он не относится конкретно к CTE и подзапросам, но применяется тот же тип концепции: если вы выбираете неинтуитивный шаблон по соображениям производительности, документируйте дерьмо из него и повторно посетите его, чтобы убедиться, что обнаруженная вами причуда все еще реальна. Я мог бы даже предложить оставить более естественную версию запроса закомментированной, если только у вас нет надежной системы контроля версий, которая поддерживает предыдущую версию.
Аарон Бертран
1
Фиксированная ссылка выше: sqlskills.com/blogs/bobb/…
ADJenks
19

#tetemateralized, а CTE - нет.

CTE - это просто синтаксис, поэтому в теории это просто подзапрос. Выполнено # Темп материализуется. Поэтому дорогой CTE в соединении, которое выполняется многократно, может быть лучше в #temp. С другой стороны, если это простая оценка, которая не выполняется, но несколько раз, то она не стоит накладных расходов на #temp.

Некоторые люди на SO не любят табличные переменные, но они мне нравятся, так как они материализованы и быстрее создаются, чем #temp. Есть моменты, когда оптимизатор запросов работает лучше с #temp по сравнению с табличной переменной.

Возможность создания PK для переменной #temp или таблицы дает оптимизатору запросов больше информации, чем CTE (поскольку вы не можете объявить PK в CTE).

папараццо
источник
что такое аббревиатура "TVP" ... что-то похожее на #temp?
Whytheq
TVP становится общим термином, потому что звучит впечатляюще (для некоторых). Короче говоря, TVP - это таблица, передаваемая как параметр. Любой, кто использовал переменные таблицы, будет с ними дома.
WonderWorker
1
ВНИМАНИЕ - у TVP нет планов выполнения! Не используйте TVP для чего-либо еще, кроме самых простых коротких списков поиска. Если вы делаете какие-либо сложные объединения, вставки или обновления, вы можете столкнуться с серьезными проблемами оптимизации. Поверь мне, я был сожжен этим.
совпадающий с заходом солнца
12

Вот две вещи, которые, я думаю, делают ВСЕГДА предпочтительным использование таблицы # Temp, а не CTE:

  1. Вы не можете поместить первичный ключ в CTE, поэтому данные, к которым обращается CTE, должны будут проходить по каждому из индексов в таблицах CTE, а не просто обращаться к PK или Index в таблице временных данных.

  2. Поскольку вы не можете добавлять ограничения, индексы и первичные ключи в CTE, они более подвержены ошибкам и плохим данным.


в понедельник, когда вчера

Вот пример, где ограничения #table могут предотвратить плохие данные, что не имеет место в CTE

DECLARE @BadData TABLE ( 
                       ThisID int
                     , ThatID int );
INSERT INTO @BadData
       ( ThisID
       , ThatID
       ) 
VALUES
       ( 1, 1 ),
       ( 1, 2 ),
       ( 2, 2 ),
       ( 1, 1 );

IF OBJECT_ID('tempdb..#This') IS NOT NULL
    DROP TABLE #This;
CREATE TABLE #This ( 
             ThisID int NOT NULL
           , ThatID int NOT NULL
                        UNIQUE(ThisID, ThatID) );
INSERT INTO #This
SELECT * FROM @BadData;
WITH This_CTE
     AS (SELECT *
           FROM @BadData)
     SELECT *
       FROM This_CTE;
ShanksPranks
источник
3
ALWAYSслишком далеко, но спасибо за ответ. С точки зрения читабельности использование CTE может быть хорошей вещью.
Whytheq
3
Я не понимаю вашего второго пункта вообще. На мой взгляд, запрос, определяющий CTE, аналогичен ограничениям, которые вы бы наложили на временную таблицу, отмечая, что первый может содержать произвольно сложные предикаты, тогда как последний гораздо более ограничен (например, CHECKограничение, ссылающееся на несколько строк / таблиц, не допускается). Можете ли вы опубликовать пример, в котором CTE обнаруживает ошибку, которой нет в таблице временных эквивалентов?
понедельник,