Підзапроси проти приєднується


158

Я відновив повільний розділ програми, яку ми отримали у спадок від іншої компанії, щоб використовувати внутрішнє з'єднання замість підзапиту типу:

WHERE id IN (SELECT id FROM ...)

Повторно налаштований запит працює приблизно в 100 разів швидше. (~ 50 секунд до ~ 0,3) Я очікував на покращення, але хтось може пояснити, чому це було так різко? Стовпці, що використовуються в пункті, де всі були індексовані. Чи виконує SQL запит у пункті де раз на рядок чи щось таке?

Оновлення - Поясніть результати:

Різниця полягає у другій частині запиту "де id in ()" -

2   DEPENDENT SUBQUERY  submission_tags ref st_tag_id   st_tag_id   4   const   2966    Using where

vs 1 індексований рядок з приєднанням:

    SIMPLE  s   eq_ref  PRIMARY PRIMARY 4   newsladder_production.st.submission_id  1   Using index

4
Можливий дублікат запиту Join vs.
Subver

2
Не дублікат. Це питання спеціально стосується вражаючої різниці в ефективності. Інше питання є більш загальним, відкритим про плюси і мінуси кожного підходу і чому один підхід здається більш популярним.
Василь Бурк

@simhumileco Це не поліпшення, це не різниця, це суперечить тому, що автор написав, і такий тип редагування для стилю коду недоречний. Коли я повинен внести зміни в код?
philipxy

Привіт @philipxy, я не мав наміру втручатися в думку автора, а лише зробити фрагмент коду більш зрозумілим і написаним ретельніше.
simhumileco

Відповіді:


160

"Корельований підзапит" (тобто такий, у якому умова, де залежить від значень, отриманих із рядків запиту, що містить), буде виконуватися один раз для кожного рядка. Некорельований підзапит (той, у якому умова, де умова не залежить від містять запит) буде виконуватися один раз на початку. Двигун SQL робить це розрізнення автоматично.

Але, так, план-пояснення дасть вам брудні деталі.


3
Зверніть увагу, що це DEPENDENT SUBQUERYозначає те саме, що і "співвіднесений запит".
Тімо

38

Ви виконуєте підзапит один раз для кожного рядка, тоді як об'єднання відбувається в індексах.


5
Я не думаю, що це правда. Двигун SQL повинен запускати підзапит лише один раз і використовувати результат як список.
дакракот

8
Це залежить - якщо підзапит якось співвідноситься із зовнішнім запитом (використовує його дані), він виконується з кожним рядком.
qbeuek

4
Мабуть, це правда в цьому випадку, але це неправда взагалі.
Емі Б

1
ОП EXPLAINзаявляє DEPENDENT SUBQUERY, що є найяскравішим показником такої поведінки.
Тімо


7

Запустіть план пояснення на кожній версії, він підкаже, чому.


6

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

Не побачивши запиту, важко сказати, що було так погано в оригіналі, але я гадаю, що це було те, що оптимізатор просто не міг зробити набагато кращого. Запуск "Пояснення" покаже вам метод оптимізаторів для отримання даних.


4

Подивіться план запитів кожного запиту.

Де вхід і приєднання зазвичай можуть бути реалізовані за допомогою одного і того ж плану виконання, тому зазвичай між ними змінюється нульова швидкість.


3
Ха-ха, я <3 Sql скраби, які голосують вниз, оскільки вони не знають, як читати плани запитів.
Емі Б

4

Оптимізатор зробив не дуже гарну роботу. Зазвичай вони можуть бути перетворені без різниці, і оптимізатор може це зробити.


4

Зазвичай результат його оптимізатора не в змозі зрозуміти, що підзапит може бути виконаний як об'єднання, і в цьому випадку він виконує підзапит для кожного запису в таблиці, а не приєднується до таблиці в підзапиті проти таблиці, яку ви запитуєте. Деякі з більш «підприємливих» баз кращі в цьому, але іноді вони все-таки сумують.


4

Це питання дещо загальне, тому ось загальна відповідь:

В основному, запити займають більше часу, коли MySQL має багато рядків для сортування.

Зробити це:

Запустіть ПОЯСНЕННЯ про кожен із запитів (JOIN'ed, потім Subqueried) та опублікуйте результати тут.

Я думаю, що побачити різницю в інтерпретації MySQL цих запитів було б досвідом навчання для всіх.


4

Підзапит де повинен виконувати 1 запит для кожного повернутого рядка. Внутрішнє з'єднання просто має виконати 1 запит.


3

Підзапит, ймовірно, виконував "сканування повної таблиці". Іншими словами, не використовуючи індекс і не повертаючи занадто багато рядків, які Де з головного запиту потрібно було фільтрувати.

Просто здогадка без деталей, звичайно, але це звичайна ситуація.


2

За допомогою підзапиту вам потрібно повторно виконати 2-й SELECT для кожного результату, і кожне виконання, як правило, повертає 1 рядок.

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


2

Це не стільки запит, скільки пункт IN, хоча приєднання лежать в основі принаймні SQL-двигуна Oracle і працюють надзвичайно швидко.


1
де насправді не властиво погано.
Шон

2

Взяте з Довідкового посібника ( 14.2.10.11 Переписування підзапитів як приєднання ):

ПРИЄДНАЙТЕСЬ ЛІВО [ВАРТУ] може бути швидше, ніж еквівалентний підзапит, оскільки сервер, можливо, зможе краще оптимізувати його - факт, який не характерний лише для MySQL Server.

Таким чином, підзапити можуть бути повільнішими, ніж ЛІВНІ [ВАРІНТИ] ПРИЄДНАЄТЬСЯ.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.