Чи є різниця в ефективності між CTE, підзапитом, тимчасовою таблицею або змінною таблиці?


222

У цьому відмінному SO питання , відмінності між CTEі sub-queriesбули обговорені.

Я хотів би спеціально запитати:

За яких обставин кожен із наступних є більш ефективним / швидшим?

  • CTE
  • Підзапит
  • Тимчасовий стіл
  • Змінна таблиця

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

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


EDIT

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


4
Загальна відповідь: це залежить. І це залежить від деяких численних факторів, будь-яке загальне твердження, ймовірно, помилкове - в деяких ситуаціях. В основному: вам потрібно перевірити і виміряти - подивіться, що найкраще підходить для вас!
marc_s

@marc_s - добре; можливо, це питання слід закрити, оскільки воно є суб'єктивним? Зверніть увагу, що багато питань SQL щодо SO можна оцінити як суб'єктивні.
чомуз

1
Це може закритися як занадто широке - і я згоден з вами - багато речей і тем у SQL дійсно отримають відповідь на це залежно . Іноді можна перерахувати два-три критерії, щоб прийняти рішення, але з вашим запитанням тут, поруч із неможливим дати обґрунтовану пораду - це залежить від стільки - вашої структури таблиць, даних у цих таблицях, запитів, якими ви користуєтесь, ваша стратегія індексування та багато іншого ...
marc_s

@marc_s, було б добре спробувати дотриматись - будь-яка порада щодо можливих змін до ОП, щоб спробувати зробити її більш конкретною та вузькою?
чомуз

Зверніть увагу, що це питання стосується SQL Server. Для інших БД, таких як постгреси, 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 виявляється досить суб'єктивним питанням; Я сподіваюся, що він не закриється за занадто розпливчасті, оскільки відповіді поки що є інформативними. Я розумію :-) вам це не подобається, коли питання змінюються, але чи є у вас якісь пропозиції щодо звуження питання в ОП?
Чому

2
Я думаю, що це питання добре, ви помітите, що ще немає жодного голосу, який потрібно закрити, але якщо відповіді почнуть дико бавитися, воно, ймовірно, закриється. Як я запропонував у своїй відповіді, якщо у вас є конкретний випадок, коли ви бачите велику різницю між CTE і підзапитом, починайте нове запитання з власними запитами та планами виконання (і це може бути краще підходить на dba.se ) . Просто зрозумійте, що відповідь на допомогу з цим запитом може бути не однаковою відповіддю на інший запит з тим самим сценарієм.
Аарон Бертран

Прямо під вашим запитанням є посилання link / edit / close / flag- якщо було закрито питання, ви побачите, close (n)де nвідображається кількість користувачів, які проголосували за закриття вашого питання. Якщо ви натиснете на посилання, ви побачите причини, які вибрали ці користувачі.
Аарон Бертран

@whytheq також дивіться цю недавню публікацію в блозі Боб Бошемін . Він не трактує CTE проти підзапиту спеціально, але застосовується та сама концепція: якщо ви вибираєте неінтуїтивний зразок з міркувань продуктивності, задокументуйте це лайно і повторно відвідайте його, щоб переконатися, що знайдений вами видовище залишається справжнім. Я навіть можу запропонувати залишити коментар із більш натуральною версією запиту, якщо у вас немає надійної системи управління джерелом, яка містить попередню версію.
Аарон Бертран

1
Виправлене посилання вище: sqlskills.com/blogs/bobb/…
ADJenks

19

#temp матералізований, а CTE - ні.

CTE - це просто синтаксис, тому теоретично це лише підзапит. Він виконується. #temp матеріалізується. Тож дорогий CTE в з'єднанні, який виконується багато разів, може бути кращим за #temp. З іншого боку, якщо це проста оцінка, яка не виконується, але кілька разів, тоді не варто накладних витрат на #temp.

Деякі люди на SO, яким не подобаються змінні таблиці, але мені подобається, що вони матеріалізуються і швидше створюються, ніж #temp. Бувають випадки, коли оптимізатор запитів робить краще з #temp порівняно зі змінною таблиці.

Можливість створення ПК на #temp або таблиці змінної дає оптимізатору запитів більше інформації, ніж CTE (оскільки ви не можете оголосити PK на CTE).


що таке абревіатура "TVP" ... щось подібне до #temp?
чомуз

TVP стає загальним терміном, тому що це звучить вражаюче (для деяких). Коротше кажучи, TVP - це таблиця, передана як параметр. Кожен, хто використовував змінні таблиці, буде вдома разом із ними.
чудотворця

1
ПОПЕРЕДЖЕННЯ - TVP не мають планів виконання! Не використовуйте ТВП ні для чого іншого, найпростішого із коротких списків пошуку. Якщо ви зробите якісь складні з'єднання, вставки або оновлення на них, ви можете зіткнутися з масовими проблемами оптимізації. Повірте, мене це спалило.
Геліяк

12

Тільки дві речі, на які я думаю, завжди дозволяють використовувати # Temp Table, а не CTE:

  1. Не можна ставити первинний ключ на CTE, тому дані, до яких звертається CTE, повинні будуть перетинати кожен з індексів у таблицях CTE, а не просто отримувати доступ до ПК або Індексу в таблиці темпів.

  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;

3
ALWAYSтрохи занадто далеко, але дякую за відповідь. Що стосується читабельності, використання CTE може бути хорошою справою.
Чому

3
Я взагалі не розумію вашого другого пункту. Як я це бачу, запит, що визначає CTE, аналогічний тим обмеженням, які ви мали б надати temp-таблиці, зазначаючи, що перший може містити довільно складні предикати, тоді як останній набагато більш обмежений (наприклад, CHECKобмеження, що стосується кількох рядків / таблиць, є не дозволено). Чи можете ви навести приклад, коли CTE виявляє помилку, якої немає в еквіваленті темп-таблиці?
onedaywhen
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.