Есть ли разница в производительности между CTE, Sub-Query, Temporary Table или Table Variable?

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

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

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

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

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

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


EDIT

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

Ответ 1

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

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

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

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

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

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

Ответ 2

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

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

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

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

Ответ 3

#temp является обеими, а CTE - нет.

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

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

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

Ответ 4

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

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

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


-понятно, что вчера

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

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;