Отдельные запросы быстрее, чем объединения?

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 |

Запрос Б :

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 возвращает то же количество полезной информации, что и запрос C.
  • A + B должен вернуть клиенту 1 + 2x3 == 7 «ячеек данных»
  • C должен вернуть клиенту 3x3 == 9 «ячеек данных», потому что при объединении я естественно включаю некоторую избыточность в набор результатов.

Обобщая из этого (насколько это возможно):

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

Из этого следует, что когда я наблюдаю, что разбиение запроса на стороне клиента на несколько запросов дает лучшую производительность, это просто путь, или это скорее означает, что я испортил объединенный запрос?

Мартин
источник
Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
Джек Дуглас
1
Я провел тест и опубликовал результаты в статье на Medium . Я бы добавил здесь ответ, но уже сделал это по другому вопросу , и публикация одного и того же ответа на несколько вопросов осуждается .
Бенджамин

Ответы:

45

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

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

Тем не менее, это почти всегда тот случай, когда объединенный результирующий набор из правильно настроенной базы данных будет быстрее и лучше масштабируется, чем возвращать исходные строки клиенту и затем объединять их там. В частности, если входные наборы велики, а набор результатов мал - подумайте о следующем запросе в контексте обеих стратегий: объедините две таблицы по 5 ГБ каждая с набором результатов из 100 строк. Это крайность, но ты видишь мою точку зрения.

Я заметил, что когда мне нужно получить информацию из нескольких таблиц, «часто» быстрее получить эту информацию с помощью нескольких запросов к отдельным таблицам (возможно, содержащих простое внутреннее объединение) и соединить данные вместе на стороне клиента, чтобы попробовать написать (сложный) объединенный запрос, где я могу получить все данные в одном запросе.

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

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

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

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

Поскольку база данных должна объединять данные, для больших наборов данных можно предположить, что база данных должна выполнять больше работы над одним объединенным запросом, чем над отдельными, поскольку (по крайней мере) она должна возвращать больше данных клиенту.

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

Из этого следует, что когда я наблюдаю, что разбиение запроса на стороне клиента на несколько запросов дает лучшую производительность, это просто путь, или это скорее означает, что я испортил объединенный запрос?

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

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

Одна вещь, которая нигде не упоминалась, это поддержание согласованности в данных, возвращаемых из базы данных. Если используются отдельные запросы, более вероятно (из-за многих факторов) возвращать несогласованные данные, если только для каждого набора запросов не используется форма изоляции моментального снимка.

Джон Сайгель
источник
+1 для пропускной способности сети также является конечным ресурсом.
Хари Харкер
OP говорит, что результирующие наборы данных JOINed всегда больше. > Объединенный запрос всегда должен возвращать больше данных, чем отдельные запросы. Я думаю, что это объективно верно (для> =), например, наборы результатов отличаются по размеру, поэтому больше данных по сети. У вас есть пример, где это не так? Если я присоединюсь к Authors -> Posts and Authors, у меня есть поле, называемое «biography», которое представляет собой поле JSON размером 1 МБ, для автора из 100 сообщений по проводной сети я передам 100 МБ против 1 МБ. Это неправильно?
Hytromo
6

Конечно, я не измерял производительность с этими

Вы собрали хороший пример кода. Вы смотрели на время в SQL Fiddle? Даже некоторое краткое ненаучное тестирование производительности покажет, что запрос три в вашей демонстрации занимает примерно столько же времени, что и запрос один или два по отдельности. Объединение одного и двух занимает примерно вдвое больше трех, то есть до того, как будет выполнено любое соединение на стороне клиента.

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

Также следует учитывать, что произойдет, если внутреннее объединение уничтожит данные.

Ли Риффель
источник
2

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

Большая часть расчетов, сделанных для нахождения лучшего плана, определяется распределением данных в таблицах. Эти распределения выбираются и хранятся в виде объектов статистики. Если это не так, они приводят оптимизатор к неправильному выбору. Плохой выбор в начале плана приводит к еще худшему выбору в последствии в виде снежного кома.

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

Майкл Грин
источник
-3

Несколько запросов это путь. Если вы работаете с такими простыми сценариями - затраты на оптимизатор запросов являются фактором. При большем количестве данных возникает неэффективность объединения (избыточные строки). Эффективность достигается только при гораздо большем количестве данных.

В конце концов, то, что вы испытываете, - это то, что видят многие разработчики. Администраторы базы данных всегда говорят «нет, сделайте объединение», но реальность такова: в этом случае быстрее сделать несколько простых выборов.

TomTom
источник
5
В объединении нет «неэффективности сети» - все это происходит на сервере базы данных, поэтому сеть не задействуется (если вы не присоединяетесь по каналу DB!)
Крис Саксон,
2
Вы могли бы рассмотреть, имеет ли сетевой уровень сжатие или нет. Oracle SQL * Net делает то, что значения, повторяющиеся в одном столбце, эффективно сжимаются.
Дэвид Олдридж
3
@ TomTom у вас может быть точка зрения или нет (как указывает Дэвид Олдридж, сжатие имеет значение), но ваша формулировка сбивает с толку. "неэффективность сетевого соединения" ? Действительно, исправь это так, чтобы было очевидно, что ты имеешь в виду.
ypercubeᵀᴹ
@ChrisSaxon, конечно, есть, у вас есть таблицы для отчета "title-> base-> table-row", и вам нужны все строки, чтобы вы могли внутренне объединить эти 3 таблицы. В каждой таблице есть длинные строки, поэтому для каждой строки вы повторяете эти длинные строки. Прикладному уровню необходимо выделить память для всех этих строк, а затем сгруппировать их для вашей модели. Поэтому я думаю, что он имеет в виду, что отправлено больше данных
МАЙК
@MIKE, который зависит от выбранных вами выражений, а не от соединения. И может быть сжатие сети. В Oracle Database SQL * Net удаляет повторяющиеся повторяющиеся значения nicetheory.io/2018/01/11/…
Крис Саксон