JOIN запросы против нескольких запросов


182

Являются ли запросы JOIN быстрее, чем несколько запросов? (Вы запускаете свой основной запрос, а затем запускаете множество других SELECT на основе результатов вашего основного запроса)

Я спрашиваю, потому что присоединение к ним усложнит ОЧЕНЬ дизайн моего приложения

Если они быстрее, может ли кто-нибудь приблизительно приблизиться к тому, насколько? Если это в 1,5 раза, мне все равно, но если это в 10 раз, я думаю, что да.


Я предполагаю, что они будут быстрее. Я знаю, что один INSERT по сравнению с 10 отдельными INSERT-запросами намного быстрее.
Алекс

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

Ответы:


84

Это слишком расплывчато, чтобы дать вам ответ, соответствующий вашему конкретному случаю. Это зависит от многих вещей. Джефф Этвуд (основатель этого сайта) на самом деле написал об этом . По большей части, тем не менее, если у вас есть правильные индексы и вы правильно выполняете свои СОЕДИНЕНИЯ, обычно будет быстрее выполнить 1 поездку, чем несколько.


2
если вы объединяете 3 или более таблиц на разных ключах, часто базы данных (например, mysql) могут использовать только один индекс на таблицу, что означает, что, возможно, одно из соединений будет быстрым (и будет использовать индекс), тогда как другие будут чрезвычайно медленными. Для нескольких запросов вы можете оптимизировать индексы для использования в каждом запросе.
user151975

4
Я думаю, что это зависит от вашего определения «быстрее» ... например, 3 внутренних соединения могут обернуться быстрее, чем 4 обхода, из-за перегрузки сети, а также потому, что вам нужно остановиться, подготовить и отправить каждый запрос после предыдущий запрос завершен. Однако, если вам нужно было тестировать сервер под нагрузкой, в большинстве случаев объединения занимают больше процессорного времени по сравнению с запросами PK, а также часто приводят к увеличению нагрузки на сеть.
mindplay.dk

98

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

  1. Один запрос с 5 объединениями

    запрос: 8,074508 секунд

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

  2. 5 запросов подряд

    время комбинированного запроса: 0,00262 секунды

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

,

Обратите внимание, что мы получаем одинаковые результаты в обоих случаях (6 х 50 х 7 х 12 х 90 = 2268000)

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

Ограничение памяти может быть не таким плохим, если вы объединяете только две таблицы, но, как правило, три или более, и это становится полезным для разных запросов.

Как примечание, мой сервер MySQL находится рядом с моим сервером приложений ... поэтому время подключения незначительно. Если ваше время соединения в секундах, то, возможно, есть преимущество

Фрэнк


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

12
@cHao говорит кто? Я только что посмотрел SMF и phpBB и увидел JOIN между 3 таблицами - если вы добавите плагины или модификации, они могут легко добавить к этому. Любое крупное приложение имеет потенциал для многих JOIN. Возможно, плохо написанный / неправильно использованный ORM может присоединиться к таблицам, которые ему на самом деле не нужны (возможно, даже к каждой таблице).
Натали Адамс

5
@NathanAdams: Левые и внутренние соединения совсем не плохи. (На самом деле, если вы не объединяете таблицы здесь и там, вы делаете неправильно SQL.) Я говорил о перекрестных объединениях , которые почти всегда нежелательны даже между двумя таблицами, не говоря уже о 5 - и которые быть о единственном способе получить иначе - полностью поддельные результаты "2268000", упомянутые выше.
Цао

2
Посмотрите на результаты, хотя. msgstr "размер результата: 2268000" против "размер результата: 165". Я думаю, что ваше замедление с JOIN связано с тем, что ваши записи имеют отношение один ко многим друг с другом, в то время как если бы они имели отношение один к одному, JOIN был бы абсолютно намного быстрее и, конечно, не имел бы результата. размер больше, чем SELECT.
HoldOffHunger

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

27

Этот вопрос старый, но в нем отсутствуют некоторые критерии. Я сравнил JOIN с его 2 конкурентами:

  • N + 1 запросов
  • 2 запроса, второй с использованием WHERE IN(...)или эквивалентный

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

ПРИСОЕДИНЯЙТЕСЬ К ГДЕ В НУ + 1

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

JOIN vs N + 1 - все записи, указывающие на одну и ту же внешнюю запись

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

вынос:

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

Смотрите мою статью на Medium для получения дополнительной информации.


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

Провел быстрый тест, выбрав одну строку из таблицы строк 50000 и соединившись с одной строкой из таблицы строк 100000. В основном выглядело так:

$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 операций чтения, тогда как на моем медленном домашнем компьютере JOIN занял 2,0 секунды. INNER JOIN и LEFT JOIN ничего не изменили. Выборка нескольких строк (например, с использованием IN SET) дала аналогичные результаты.


1
Возможно, разница может измениться, если выбрать страницу с строками (например, 20 или 50), как для типичной сетки веб-представления, и сравнить одиночный LEFT JOIN с двумя запросами - выбрать 2 или 3 идентификатора с некоторыми критериями WHERE, а затем запустить другой ВЫБЕРИТЕ запрос с помощью IN ().
JustAMartin

Индексируются ли столбцы id и other_id?
Ариш Рамеш

11

Реальный вопрос: есть ли у этих записей отношение один к одному или отношение один ко многим ?

Ответ TLDR:

Если один на один, используйте JOINутверждение.

Если один ко многим, используйте один (или много) SELECTоператоров с оптимизацией кода на стороне сервера.

Почему и как использовать SELECT для оптимизации

SELECTИспользование (с несколькими запросами вместо объединений) для большой группы записей на основе отношения «один ко многим» обеспечивает оптимальную эффективность, поскольку в случае с JOINпроблемой экспоненциальной утечки памяти. Соберите все данные, а затем используйте язык сценариев на стороне сервера, чтобы разобраться в них:

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, Tag и Comment.

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/en/wb-relationship-tools.html


Вы упускаете суть. Дело не в одном (один | много). Речь идет о том, имеет ли смысл наборы строк в паре вместе. Вы запрашиваете только два тангенциально связанных набора данных. Если вы запрашивали комментарии и, скажем, контактную информацию их авторов, это имеет больше смысла как объединение, даже если люди могут предположительно написать более одного комментария.
Цао

@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 для этого.
Цао

8

Создайте как отдельные запросы, так и объединения, а затем оцените каждый из них - ничто не поможет больше, чем реальные цифры.

Тогда еще лучше - добавьте «EXPLAIN» в начало каждого запроса. Это скажет вам, сколько подзапросов MySQL использует для ответа на ваш запрос данных, и сколько строк проверено для каждого запроса.


7

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

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

Опять же, если изменение его на JOIN будет означать дополнительный день / неделю / месяц работы разработчика, я бы придерживался нескольких SELECT

Ура,

BLT


5

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

При взаимодействии с базой данных из другого приложения, такого как PHP, существует аргумент одной поездки на сервер из-за многих.

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

Я не новичок в том, что касается SQL, я думаю, что разработчики, особенно юниоры, склонны тратить много времени, пытаясь написать очень умные объединения, потому что они выглядят умными, тогда как на самом деле есть умные способы извлечения данных, которые выглядят просто.

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


Да, мы также должны учитывать не только сами запросы, но и обработку данных внутри приложения. При извлечении данных с помощью внешних объединений существует некоторая избыточность (иногда она может стать очень большой), которая должна быть отсортирована приложением (обычно в некоторой библиотеке ORM), таким образом, в итоге, единственный запрос SELECT с JOIN может потреблять больше ресурсов ЦП и время, чем два простых ВЫБОРЫ
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

3

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


2

Вот ссылка со 100 полезными запросами, они протестированы в базе данных Oracle, но помните, что SQL - это стандарт, который отличается от Oracle, MS SQL Server, MySQL и других баз данных SQL-диалектом:

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


1

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

Реальный вопрос - как вы хотите получить доступ к данным? Single выбирает поддержку позднего связывания. Например, если вам нужна только информация о сотруднике, вы можете выбрать ее из таблицы «Сотрудники». Отношения внешнего ключа могут использоваться для извлечения связанных ресурсов позднее и по мере необходимости. У селекторов уже будет ключ для указания, поэтому они должны быть очень быстрыми, и вам нужно только получить то, что вам нужно. Сетевая задержка всегда должна учитываться.

Объединения будут извлекать все данные одновременно. Если вы создаете отчет или заполняете сетку, это может быть именно тем, что вам нужно. Скомпилированные и оптомизированные объединения просто будут быстрее, чем одиночные выборки в этом сценарии. Помните, что специальные объединения могут быть не такими быстрыми - вы должны скомпилировать их (в сохраненный процесс). Скорость ответа зависит от плана выполнения, который точно определяет, какие шаги СУБД предпринимает для извлечения данных.


0

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.