ПРИЄДНАЙТЕ запити проти кількох запитів


180

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

Я прошу, тому що приєднання до них ускладнило б ДЛЯ проекту моєї заявки

Якщо вони швидші, може хтось приблизно приблизно наблизитися? Якщо це 1,5x, мені все одно, але якщо це 10x, я думаю, що це робити.


Я припускаю, що вони були б швидшими. Я знаю, що один INSERT порівняно з 10 заявками на INSERT набагато швидше.
alex

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

Відповіді:


82

Це занадто розпливчасто, щоб дати відповідь, що відповідає вашому конкретному випадку. Це залежить від багатьох речей. Про це насправді писав Джефф Етвуд (засновник цього сайту) . Здебільшого, якщо у вас є правильні індекси та ви правильно робите свої ПРИЄДНАННЯ, зазвичай це буде швидше зробити 1 поїздку, ніж кілька.


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

4
Я думаю, це залежить від вашого визначення поняття "швидше" ... наприклад, 3 внутрішніх з'єднання PK можуть обернутися швидше, ніж 4 обхідні маршрути, через накладні витрати мережі та тому, що вам потрібно зупинити і підготувати та надіслати кожен запит після попередній запит завершується. Якщо ви орієнтували сервер під навантаженням, однак, у більшості випадків приєднання забирає більше процесорного часу та PK запитів, а також часто спричиняє більше накладних витрат на мережу.
mindplay.dk

97

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

  1. Один запит з 5 приєднаннями

    запит: 8.074508 секунд

    розмір результату: 2268000

  2. 5 запитів підряд

    комбінований час запиту: 0,00262 секунди

    розмір результату: 165 (6 + 50 + 7 + 12 + 90)

.

Зауважте, що ми отримуємо однакові результати в обох випадках (6 x 50 x 7 x 12 x 90 = 2268000)

ліві об'єднання використовують експоненціально більше пам’яті із зайвими даними.

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

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

Френк


31
Якщо ми відкинемо набридлий маленький факт, що ніхто з розуму не перехрещується між 5 столами (саме тому, поряд з тим, що в більшості випадків це просто не має сенсу ), ваш «орієнтир» може мати певну заслугу . Але ліві або внутрішні приєднання - це норма, як правило, за ключем (пошук стає набагато швидшим), а дублювання даних зазвичай набагато, набагато менше, ніж ви це робите.
cHao

12
@cHao каже хто? Я щойно подивився на SMF та phpBB і побачив ПРИЄДНАЙТЕСЬ між трьома таблицями - якщо ви додасте плагіни чи модифікації, вони легко додадуть до цього. Будь-яка велика програма має потенціал для багатьох ПРИЄДНАЙТЕСЬ. Можливо, погано записаний / неправильно використаний ORM може приєднатися до таблиць, які йому насправді не потрібні (можливо, навіть кожна таблиця).
Наталі Адамс

5
@NathanAdams: Ліві та внутрішні з'єднання зовсім не погані. (Насправді, якщо ви не поєднуєте таблиці тут і там, ви робите помилку SQL.) Те, про що я говорив, - це перехресні з'єднання , які майже завжди небажані навіть між двома таблицями, не кажучи вже про 5 - і які б поговоримо про єдиний спосіб отримати інакше абсолютно невірні результати "2268000", згадані вище.
cHao

2
Подивіться, однак, на результати. "розмір результату: 2268000" проти "розмір результату: 165". Я думаю, що ваше уповільнення роботи з ПРИЄДНАЙТЕСЬ є тому, що у ваших записах є стосунки один до багатьох, тоді як якби вони мали стосунки один до одного, ПРИЄДНАЙТЕ було б абсолютно швидше, і це, звичайно, не матиме результату розмір більший, ніж ВИБІР.
HoldOffHunger

3
@cHao Очевидно, що ви не зустрічалися з Magento під час вашого першого коментаря
vitoriodachef

26

Це питання давнє, але у ньому відсутні деякі орієнтири. Я відзначив приєднання до своїх 2 конкурентів:

  • N + 1 запитів
  • 2 запити, другий з використанням WHERE IN(...)або еквівалент

Результат зрозумілий: на MySQL JOINце набагато швидше. N + 1 запитів може різко знизити продуктивність програми:

ПРИЄДНУЙТЕСЬСЯ проти ВСЕ, де Н + 1

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

ПРИЄДНАЙТЕСЬ проти N + 1 - усі записи, що вказують на той же закордонний рекорд

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

Винос:

  • Для стосунків * -один, завжди використовуйте JOIN
  • Для * -в-багатьох, другий запит Міць бути швидшим

Дивіться мою статтю про "Середній" для отримання додаткової інформації.


22

Я фактично прийшов до цього питання, шукаючи відповідь, і, прочитавши відповіді, я можу лише погодитись, що найкращий спосіб порівняння продуктивності запитів БД - це отримання реальних цифр, оскільки потрібно враховувати просто багато змінних Але я також думаю, що порівняння чисел між ними призводить до користі майже у всіх випадках. Я маю на увазі те, що числа завжди слід порівнювати з прийнятним числом і, безумовно, не порівнювати один з одним.

Я можу зрозуміти, якщо один спосіб запиту займає, наприклад, 0,02 секунди, а інший - 20 секунд, це величезна різниця. Але що робити, якщо один спосіб запиту займає 0,0000000002 секунд, а інший займає 0,0000002 секунди? В обох випадках один із способів - це в 1000 разів швидше, ніж інший, але чи справді він все-таки "колосальний" у другому випадку?

Підсумок, як я особисто це бачу: якщо це добре, перейдіть на просте рішення.


4
Це, звичайно, залежно від того, плануєте ви чи ні масштабування. Тому що, коли розпочався facebook, я впевнений, що у них були такі запити, але мав на увазі масштабування і пішов на більш ефективне, хоча й більш складне рішення.
dudewad

@dudewad Має сенс. Все залежить від того, що вам потрібно, врешті-решт.
Валентин Флаксель

4
Ха-ха-так ... адже в google 1 втрата наносекунд буквально дорівнює приблизно 10 мільярдам трильйонів доларів ... але це лише чутка.
dudewad

2
@dudewad Насправді, коли Facebook запустився, я гарантую, що вони пішли з більш простим рішенням. Цукерберг сказав, що програмував першу версію лише за 2 тижні. Стартапам потрібно швидко рухатися, щоб змагатися, а ті, хто виживає, зазвичай не турбуються про масштабування, поки вони насправді цього не потребують. Потім вони рефакторні речі після того, як вони отримають мільйони інвестиційних доларів і можуть найняти програмістів Rockstar, які спеціалізуються на продуктивності. На ваш погляд, я б очікував, що Facebook часто йде на більш складне рішення щодо хвилинних підвищення продуктивності, але тоді більшість з нас не програмує Facebook.
Даллін

15

Зробив швидкий тест, вибравши один рядок із таблиці 50 000 рядків та з'єднавшись із одним рядком із 100 000 рядкових таблиць. В основному виглядав так:

$id = mt_rand(1, 50000);
$row = $db->fetchOne("SELECT * FROM table1 WHERE id = " . $id);
$row = $db->fetchOne("SELECT * FROM table2 WHERE other_id = " . $row['other_id']);

проти

$id = mt_rand(1, 50000);
$db->fetchOne("SELECT table1.*, table2.*
    FROM table1
    LEFT JOIN table1.other_id = table2.other_id
    WHERE table1.id = " . $id);

Двоє методів вибору зайняли 3,7 секунди для 50 000 прочитаних, тоді як ПРИЄДНАННЯ зайняло 2,0 секунди на моєму домашньому повільному комп'ютері. ВНУТРІШНЯ ПРИЄДНАЙТЕСЬ та НАЛЕЖНА ПРИЄДНАЙТЕСЬ не змінилися. Вибір декількох рядків (наприклад, використання IN SET) дав подібні результати.


1
Можливо, різниця може обернутися інакше, якщо вибрати сторінку рядків (наприклад, 20 або 50), як для типової сітки перегляду веб-сторінок, і порівняти один лівий приєднатися до двох запитів - вибору 2 або 3 ідентифікаторів з деякими критеріями WHERE, а потім запуску іншого ВИБІРИТЕ запит з IN ().
JustAMartin

Чи індексовані стовпці id та other_id?
Аариш Рамеш

11

Справжнє запитання: Чи мають ці записи стосунки один до одного або стосунки один до багатьох ?

Відповідь TLDR:

Якщо один на один, використовуйте JOINвислів.

Якщо один-багато-багато, використовуйте один (або багато) SELECTоператорів з оптимізацією коду на стороні сервера.

Чому і як використовувати SELECT для оптимізації

SELECT'ing (з декількома запитами замість об'єднання) для великої групи записів на основі взаємозв'язку один до багатьох виробляє оптимальну ефективність, оскільки JOIN' ing має проблему експоненціальної витоку пам'яті. Візьміть усі дані, а потім скористайтеся мовою скриптів на стороні сервера, щоб впорядкувати їх:

SELECT * FROM Address WHERE Personid IN(1,2,3);

Результати:

Address.id : 1            // First person and their address
Address.Personid : 1
Address.City : "Boston"

Address.id : 2            // First person's second address
Address.Personid : 1
Address.City : "New York"

Address.id : 3            // Second person's address
Address.Personid : 2
Address.City : "Barcelona"

Ось, я отримую всі записи, в одному виділеному заяві. Це краще, ніж те JOIN, що отримувало б невелику групу цих записів, по одному, як підкомпонент іншого запиту. Потім я розбираю його з кодом на стороні сервера, який виглядає приблизно так ...

<?php
    foreach($addresses as $address) {
         $persons[$address['Personid']]->Address[] = $address;
    }
?>

Коли не використовувати JOIN для оптимізації

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

Але JOINце неефективно при отриманні записів, які мають стосунки один до багатьох.

Приклад: Блоги в базі даних містять 3 цікаві таблиці, Blogpost, тег та коментар.

SELECT * from BlogPost
LEFT JOIN Tag ON Tag.BlogPostid = BlogPost.id
LEFT JOIN Comment ON Comment.BlogPostid = BlogPost.id;

Якщо є 1 блог, 2 теги та 2 коментарі, ви отримаєте такі результати:

Row1: tag1, comment1,
Row2: tag1, comment2,
Row3: tag2, comment1,
Row4: tag2, comment2,

Зауважте, як дублюється кожен запис. Гаразд, так, 2 коментарі та 2 теги - це 4 ряди. Що робити, якщо у нас є 4 коментарі та 4 теги? Ви не отримуєте 8 рядків - ви отримуєте 16 рядків:

Row1: tag1, comment1,
Row2: tag1, comment2,
Row3: tag1, comment3,
Row4: tag1, comment4,
Row5: tag2, comment1,
Row6: tag2, comment2,
Row7: tag2, comment3,
Row8: tag2, comment4,
Row9: tag3, comment1,
Row10: tag3, comment2,
Row11: tag3, comment3,
Row12: tag3, comment4,
Row13: tag4, comment1,
Row14: tag4, comment2,
Row15: tag4, comment3,
Row16: tag4, comment4,

Додайте більше таблиць, більше записів і т. Д., І проблема швидко розгорнеться до сотень рядків, які наповнені переважно зайвими даними.

Скільки коштують ці дублікати? Пам'ять (на SQL-сервері та код, який намагається видалити дублікати) та мережеві ресурси (між SQL-сервером та сервером коду).

Джерело: https://dev.mysql.com/doc/refman/8.0/en/nested-join-optimization.html ; https://dev.mysql.com/doc/workbench/uk/wb-relationship-tools.html


Ви пропускаєте суть. Йдеться не про те, щоб один (один | багато). Йдеться про те, чи має набір рядків сенс поєднуватись разом. Ви запитуєте два лише тангенціально пов’язані набори даних. Якщо ви запитували коментарі та, скажімо, контактну інформацію їх авторів, це має більше сенсу як приєднання, хоча люди, ймовірно, можуть написати більше одного коментаря.
cHao

@cHao: Дякуємо за ваш коментар. Моя відповідь вище - це підсумок документації MySQL, знайдений тут: dev.mysql.com/doc/workbench/en/wb-relationship-tools.html
HoldOffHunger

Це не документація на MySQL. Це документація для певного інструменту GUI для роботи з базами даних MySQL. І він не дає ніяких вказівок щодо того, коли приєднання є (або не є) доцільними.
cHao

@cHao: Вибачте, я мав на увазі документацію MySQL (R) для MySQL WorkBench (TM), а не MySQL Server (TM).
HoldOffHunger

Педантність убік, актуальність не зрозуміла. Обидва згадують стосунки один на один і один на багато, але на цьому спільність закінчується. Так чи інакше, питання стосується взаємозв'язку між наборами даних. Приєднайтеся до двох неспоріднених наборів, ви збираєтесь отримувати кожну комбінацію двох. Розбийте пов'язані дані на кілька варіантів, і тепер ви зробили кілька запитів на сумнівну користь і почали виконувати завдання MySQL для цього.
cHao

8

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

Тоді ще краще - додайте "ПОЯСНЕННЯ" до початку кожного запиту. Це покаже вам, скільки підзапитів використовує MySQL, щоб відповісти на ваш запит щодо даних та скільки рядків, сканованих для кожного запиту.


7

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

Спробуйте запустити деяку статистику бази даних як для приєднання, так і для кількох SELECTS. Подивіться, чи у вашому оточенні ПРИЄДНАЙТЕ швидше / повільніше, ніж ВИБІР.

Знову ж таки, якщо змінити його на ПРИЄДНАЙТЕ означатиме додатковий день / тиждень / місяць роботи на розробці, я б дотримувався кількох ВИБІРІВ

Ура,

BLT


5

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

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

Існують і інші способи обмежити кількість поїздок на сервер і все ще запускати кілька запитів, які часто не тільки швидше, але також полегшують читання програми - наприклад, mysqli_multi_query.

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

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


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

4

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

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

Часто це спричинено відносинами «багато-до-одного». Наприклад, відповідь HoldOffHunger згадала про один запит на повідомлення, теги та коментарі. Коментарі стосуються публікації, як і теги ..., але теги не пов'язані з коментарями.

+------------+     +---------+     +---------+
|  comment   |     |   post  |     |  tag    |
|------------|*   1|---------|1   *|---------|
| post_id    |-----| post_id |-----| post_id |
| comment_id |     | ...     |     | tag_id  |
| user_id    |     |         |     | ...     |
| ...        |     |         |     | ...     |
+------------+     +---------+     +---------+

У цьому випадку однозначно краще, щоб це було принаймні два окремих запиту. Якщо ви намагаєтеся приєднати теги та коментарі, оскільки між ними немає прямого зв’язку, ви закінчитеся з усіма можливими комбінаціями тегів та коментарів. many * many == manymany. Окрім цього, оскільки повідомлення та теги не пов'язані між собою, ви можете робити ці два запити паралельно, що призводить до потенційного виграшу.

Однак розглянемо інший сценарій: Ви хочете, щоб коментарі додавалися до публікації та контактна інформація коментаторів.

 +----------+     +------------+     +---------+
 |   user   |     |  comment   |     |   post  |
 |----------|1   *|------------|*   1|---------|
 | user_id  |-----| post_id    |-----| post_id |
 | username |     | user_id    |     | ...     |
 | ...      |     | ...        |     +---------+
 +----------+     +------------+

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


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

@AdrianBaker: Як я вже говорив, багато розумних людей докладають багато важкої роботи. Якщо я збирався оптимізувати свій SQL-сервер, першою моєю ідеєю було б використовувати стиснення, що дозволило б усунути величезну кількість надмірності без зміни коду багато чого взагалі. Оптимізація наступного рівня включатиме в себе реорганізацію результату в таблиці та надсилання їх разом з кортежами ідентифікаторів рядків, які клієнтська бібліотека потім може легко зібрати на своїй стороні за потребою.
cHao

Обидві ці оптимізації можуть творити чудеса з приєднанням, щоб зменшити або навіть усунути надмірність, але є не так багато, що може допомогти з невід'ємними послідовними запитами, які вам доведеться зробити, щоб отримати відповідні записи.
cHao

3

Чи буде швидше з точки зору пропускної здатності? Ймовірно. Але він також потенційно блокує більше об'єктів бази даних одночасно (залежно від вашої бази даних та вашої схеми) і тим самим зменшує паралельність. На мій досвід, люди часто вводять в оману аргументом "менша кількість туди-назад", коли насправді в більшості OLTP-систем, де база даних знаходиться в одній локальній мережі, справжнє вузьке місце рідко є мережею.


2

Ось посилання зі 100 корисними запитами, вони перевірені в базі даних Oracle, але пам'ятайте, що SQL є стандартом, чим відрізняються Oracle, MS SQL Server, MySQL та інші бази даних, це діалект SQL:

http://javaforlearn.com/100-sql-queries-learn/


1

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

Справжнє запитання - як ви хочете отримати доступ до даних. Одномісний вибирає підтримку з пізньою зв'язкою. Наприклад, якщо ви хочете лише інформацію про співробітників, ви можете вибрати з таблиці працівників. Іноземні ключові відносини можуть бути використані для отримання відповідних ресурсів у більш пізній час та за необхідності. У вибраних вже буде ключ, на який слід вказати, тому вони повинні бути надзвичайно швидкими, і вам залишається лише отримати те, що вам потрібно. Завжди потрібно враховувати затримку в мережі.

Приєднання одразу отримає всі дані. Якщо ви формуєте звіт або заповнюєте сітку, це може бути саме те, що ви хочете. Скомпільовані та оптимізовані приєднання просто пройдуть швидше, ніж один вибір у цьому сценарії. Пам'ятайте, що тимчасові приєднання можуть бути не настільки швидкими - ви повинні їх скласти (у збережений додаток). Відповідь про швидкість залежить від плану виконання, який детально визначає, які кроки виконує СУБД для отримання даних.


0

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

Чому б не перевірити обидва сценарії, тоді ви точно знатимете ...

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