Чи індивідуальні запити швидше, ніж приєднуються?


44

Концептуальне запитання: чи індивідуальні запити швидше, ніж приєднуються, або: Чи слід намагатися видавити кожну інформацію, яку я хочу на стороні клієнта, в один оператор SELECT або просто використовувати стільки, скільки здається зручним?

TL; DR : Якщо мій об'єднаний запит займає більше часу, ніж виконання окремих запитів, це моя помилка чи це варто очікувати?

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

Я спробував скласти один надзвичайно простий приклад разом:

SQL Fiddle

Налаштування схеми :

CREATE TABLE MASTER 
( ID INT NOT NULL
, NAME VARCHAR2(42 CHAR) NOT NULL
, CONSTRAINT PK_MASTER PRIMARY KEY (ID)
);

CREATE TABLE DATA
( ID INT NOT NULL
, MASTER_ID INT NOT NULL
, VALUE NUMBER
, CONSTRAINT PK_DATA PRIMARY KEY (ID)
, CONSTRAINT FK_DATA_MASTER FOREIGN KEY (MASTER_ID) REFERENCES MASTER (ID)
);

INSERT INTO MASTER values (1, 'One');
INSERT INTO MASTER values (2, 'Two');
INSERT INTO MASTER values (3, 'Three');

CREATE SEQUENCE SEQ_DATA_ID;

INSERT INTO DATA values (SEQ_DATA_ID.NEXTVAL, 1, 1.3);
INSERT INTO DATA values (SEQ_DATA_ID.NEXTVAL, 1, 1.5);
INSERT INTO DATA values (SEQ_DATA_ID.NEXTVAL, 1, 1.7);
INSERT INTO DATA values (SEQ_DATA_ID.NEXTVAL, 2, 2.3);
INSERT INTO DATA values (SEQ_DATA_ID.NEXTVAL, 3, 3.14);
INSERT INTO DATA values (SEQ_DATA_ID.NEXTVAL, 3, 3.7);

Запит A :

select NAME from MASTER
where ID = 1

Результати :

| NAME |
--------
|  One |

Запит B :

select ID, VALUE from DATA
where MASTER_ID = 1

Результати :

| ID | VALUE |
--------------
|  1 |   1.3 |
|  2 |   1.5 |
|  3 |   1.7 |

Запит C :

select M.NAME, D.ID, D.VALUE 
from MASTER M INNER JOIN DATA D ON M.ID=D.MASTER_ID
where M.ID = 1

Результати :

| NAME | ID | VALUE |
---------------------
|  One |  1 |   1.3 |
|  One |  2 |   1.5 |
|  One |  3 |   1.7 |

Звичайно, я не оцінював жодної продуктивності з цим, але можна помітити:

  • Запит A + B повертає таку ж кількість корисної інформації, що і Query C.
  • A + B має повернути клієнтові 1 + 2x3 == 7 "Осередки даних"
  • C повинен повернути клієнтові 3x3 == 9 "Осередки даних", тому що при об'єднанні я, природно, включаю деяку надмірність у набір результатів.

Узагальнення з цього (наскільки це можливо):

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

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


Коментарі не для розширеного обговорення; ця розмова переміщена до чату .
Джек Дуглас

1
Я запустив орієнтир і опублікував результати у статті про "Середній" . Я би тут додав відповідь, але вже робив це на інше запитання , і розміщення тієї ж відповіді на кілька запитань нахмурився .
Бенджамін

Відповіді:


45

Чи індивідуальні запити швидше, ніж приєднуються, або: Чи слід спробувати видавити кожну інформацію, яку я хочу на стороні клієнта, в один оператор SELECT або просто використовувати стільки, скільки здається зручним?

У будь-якому сценарії ефективності вам доведеться перевірити та виміряти рішення, щоб побачити, що швидше .

При цьому майже завжди буває так, що набір об'єднаних результатів із правильно налаштованої бази даних буде швидшим та масштабнішим, ніж повернення вихідних рядків клієнтові та потім приєднання до них. Зокрема, якщо набори введення великі, а набір результатів невеликий - подумайте про наступний запит у контексті обох стратегій: об’єднайте дві таблиці, об’ємом по 5 ГБ, з результатом набору 100 рядків. Це крайність, але ви бачите мою думку.

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

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

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

Зазвичай це не так. Більшість часу, навіть якщо набір входів великий, набір результатів буде набагато меншим, ніж сума входів.

Залежно від програми, дуже великі набори результатів запитів, що повертаються клієнту, є негайним червоним прапором: що робить клієнт із таким великим набором даних, який неможливо зробити ближче до бази даних? Відображення 1 000 000 рядків користувачеві, найменше, підозріло. Пропускна здатність мережі також є кінцевим ресурсом.

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

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

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

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

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

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


+1 для пропускної спроможності мережі також є кінцевим ресурсом.
Харі Харкер

ОП говорить, що набір результатів JOINed завжди більший. > Об'єднаний запит завжди повинен повертати більше даних, ніж окремі запити. Я думаю, що це об'єктивно вірно (для> =), наприклад, набори результатів відрізняються за розміром, тому більше даних по дроту. Чи є у вас приклад, коли це неправда? Якщо я приєднаюся до Авторів -> Повідомлення та Автори, має поле під назвою "біографія", яке становить 1 МБ поля JSON, для автора 100 повідомлень, через провід, я передаю 100 Мб проти 1 МБ. Це неправильно?
hytromo

6

Звичайно, я не оцінював жодної продуктивності

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

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

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


2

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

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

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


-3

Кілька запитів - це шлях. Якщо ви обробляєте такі прості сценарії, то фактор витрат на оптимізатор запитів є фактором. З більшою кількістю даних надходить мережева неефективність з'єднання (надлишкові рядки). Ефективність має лише набагато більше даних.

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


5
У об'єднанні немає "неефективності мережі" - це все відбувається на сервері баз даних, тому мережа не бере участь (якщо ви не приєднуєтесь через db-посилання!)
Кріс Саксон,

2
Ви можете розглянути питання про те, чи стискається мережевий рівень чи ні. SQL * Net Oracle робить так, що значення, що повторюються в одному стовпці, ефективно стискаються.
Девід Олдрідж

3
@TomTom у вас може бути точка чи ні (як Девід Олдрідж, компресія має значення), але ваше формулювання заплутане. "мережева неефективність об'єднання" ? Дійсно, виправте це, так що очевидно, що ви маєте на увазі.
ypercubeᵀᴹ

@ChrisSaxon впевнений, що є, зображення, у вас є таблиці для звіту "title-> base-> table-рядки", і вам потрібні всі рядки, щоб ви приєдналися до цих трьох таблиць. У кожній таблиці є довгі вархари, тому що трапляється - це для кожного ряду, який ви повторюєте ці довгі вархари. Прикладному шару потрібно виділити пам'ять для всіх цих рядків, а потім згрупувати їх для вашої моделі. Тому я думаю, що саме це він має на увазі, надсилається більше даних
МІКЕ

@MIKE, що залежить від обраних виразів, а не об'єднання. І може бути стиснення мережі. В Oracle Database SQL * Net видаляє повторювані повторювані значення nicetheory.io/2018/01/11/…
Кріс Саксон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.