Присоединиться против подзапроса

839

Я - пользователь MySQL старой школы и всегда предпочитал JOINподзапрос. Но в настоящее время каждый использует подзапрос, и я ненавижу его; Я не знаю почему.

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

Ваш здравый смысл
источник
23
Подзапросы хороши иногда. Они сосут с точки зрения производительности в MySQL. Не используйте их.
runrig
8
У меня всегда было впечатление, что подзапросы неявно выполняются как соединения, где они доступны в определенных технологиях БД.
Кеззер
18
Подзапросы не всегда отстойные, при объединении с довольно большими таблицами предпочтительным способом является выборочный выбор из этой большой таблицы (ограничение количества строк) и затем объединение.
ovais.tariq
136
«в настоящее время каждый использует подзапрос» [необходима цитата]
Писквор покинул здание
3
Потенциально связаны (хотя и более конкретны): stackoverflow.com/questions/141278/subqueries-vs-joins/…
Ли Бренецкий,

Ответы:

191

Взято из руководства MySQL ( 13.2.10.11 Перезапись подзапросов как объединений ):

LEFT [OUTER] JOIN может быть быстрее, чем эквивалентный подзапрос, потому что сервер может быть в состоянии оптимизировать его лучше - факт, который не относится только к MySQL Server.

Так что подзапросы могут быть медленнее, чем LEFT [OUTER] JOIN, но, на мой взгляд, их сила немного выше читаемости.

simhumileco
источник
45
@ user1735921 IMO, это зависит ... Вообще, очень важна читаемость кода, потому что он очень важен для последующего управления им ... Давайте вспомним известное высказывание Дональда Кнута: «Преждевременная оптимизация - корень всех зло (или хотя бы большая его часть) в программировании " . Однако, естественно, есть области программирования, где производительность имеет первостепенное значение. В идеале, когда одному удается примирить одно с другим :)
simhumileco
32
В более сложных запросах я считаю, что объединения гораздо легче читать, чем подзапросы. Подзапросы превращаются в миску лапши в моей голове.
Захра
6
@ user1735921 конечно, особенно когда запрос становится настолько сложным, что он делает не то, и вы тратите день на его исправление ... между ними, как обычно, есть баланс.
fabio.sussetto
6
@ user1735921 Только в том случае, если выигрыш в производительности оправдывает увеличение времени на обслуживание, необходимое в будущем,
Джошуа Шлихтинг,
3
Мое мнение Joinи sub queryимеет другой синтаксис, поэтому читаемость мы не можем сравнить, оба имеют более высокую читаемость, если вы хорошо разбираетесь в синтаксисе SQL. Производительность важнее.
Thavaprakash Swaminathan
844

Подзапросы - это логически правильный способ решения задач в форме «Получить факты из A, при условии наличия фактов из B». В таких случаях логичнее вставлять B в подзапрос, чем объединение. Это также более безопасно, в практическом смысле, так как вам не нужно быть осторожным в получении дублированных фактов от A из-за нескольких матчей против B.

На практике, однако, ответ обычно сводится к производительности. Некоторые оптимизаторы сосут лимоны, когда им дают соединение против подзапроса, а некоторые оптимизаторы сосут лимоны другим способом, и это зависит от оптимизатора, от версии СУБД и от запроса.

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

Марсело Кантос
источник
106
Отличный ответ. Я бы также добавил, что разработчики (особенно любительские) не всегда хорошо владеют SQL.
Альваро Гонсалес
4
+1 В поисках логического объяснения этой проблемы в течение долгого времени, это единственный ответ, который мне кажется логичным
Али Умайр
1
@Marcelo Cantos, не могли бы вы привести пример вашего заявления: «Это также более безопасно в практическом смысле, так как вам не нужно быть осторожным в получении дублированных фактов из A из-за нескольких совпадений с B.»? Я нашел это очень проницательным, но слишком абстрактным. Спасибо.
Цзинхуэй Ниу
6
@JinghuiNiu Клиенты , которые купили дорогие вещи: select custid from cust join bought using (custid) where price > 500. Если клиент купил несколько дорогих товаров, вы получите удвоение. Чтобы это исправить select custid from cust where exists (select * from bought where custid = cust.custid and price > 500). Вы можете использовать select distinct …вместо этого, но это часто больше работы, либо для оптимизатора или оценщика.
Марсело Кантос
1
@ MatTheWhale да, я использовал упрощенный ответ, потому что я был ленив. В реальном сценарии вы вытягиваете больше столбцов, чем просто кус-кус из каста.
Марсело Кантос
358

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

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

Хорошая вещь в подзапросах состоит в том, что они более читабельны, чем JOINs: именно поэтому большинство новых людей SQL предпочитают их; это простой способ; но когда дело доходит до производительности, JOINS лучше в большинстве случаев, хотя их тоже нетрудно прочитать.

Kronass
источник
14
Да, поэтому большинство баз данных включает его в качестве шага оптимизации для преобразования подзапросов в объединения при анализе вашего запроса.
Cine
16
Этот ответ слишком упрощен для вопроса, который был задан. Как вы утверждаете: некоторые подзапросы в порядке, а некоторые нет. Ответ не очень помогает различить два. (также «очень редкий» действительно зависит от ваших данных / приложения).
причины
21
Можете ли вы доказать какую-либо из ваших точек зрения с помощью документации или результатов испытаний?
Угур Гюмюшан
62
Я получил очень хороший опыт работы с подзапросами, которые содержат обратную ссылку на верхний запрос, особенно когда дело касается числа строк выше 100 000. Все дело в использовании памяти и подкачке файла подкачки. Объединение будет производить очень большой объем данных, которые могут не помещаться в память и должны быть выгружены в файл подкачки. Всякий раз, когда это так, время запроса для небольших подвыборов, таких как select * from a where a.x = (select b.x form b where b.id = a.id), чрезвычайно мало по сравнению с объединением. Это очень специфическая проблема, но в некоторых случаях она приносит вам от часов до минут.
Зуло
13
У меня есть опыт работы с Oracle, и я могу сказать, что подзапросы гораздо лучше подходят для больших таблиц, если у вас нет фильтрации или сортировки по ним.
Амир Пашазаде
130

Используйте EXPLAIN, чтобы увидеть, как ваша база данных выполняет запрос к вашим данным. Существует огромное "это зависит" в этом ответе ...

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

Фрэнк Хейкенс
источник
6
именно поэтому postgresql настолько хорош и полезен, что понимает, в чем заключается цель, и исправит запрос, основываясь на том, что он считает лучше, а postgresql очень хорошо знает, как смотреть на свои данные
WojonsTech
heww. Я думаю, нет необходимости переписывать тонны запросов для меня! Postgresql для победы.
Даниэль Шин
77

В 2010 году я присоединился бы к автору этого вопроса и за него бы решительно проголосовал JOIN, но с гораздо большим опытом (особенно в MySQL) я могу сказать: да, подзапросы могут быть лучше. Я прочитал несколько ответов здесь; некоторые заявленные подзапросы выполняются быстрее, но им не хватает хорошего объяснения. Я надеюсь, что смогу дать один (очень) поздний ответ:

Прежде всего, позвольте мне сказать самое важное: существуют различные формы подзапросов

И второе важное утверждение: размер имеет значение

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

Подзапросы в полях выбора

SELECT moo, (SELECT roger FROM wilco WHERE moo = me) AS bar FROM foo

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

Подзапросы в выражении Where

SELECT moo FROM foo WHERE bar = (SELECT roger FROM wilco WHERE moo = me)

Если вам повезет, БД оптимизирует это внутренне в JOIN. Если нет, ваш запрос станет очень, очень медленным для огромных наборов данных, потому что он будет выполнять подзапрос для каждой строки foo, а не только для результатов, как в типе выбора.

Подзапросы в сообщении Join

SELECT moo, bar 
  FROM foo 
    LEFT JOIN (
      SELECT MIN(bar), me FROM wilco GROUP BY me
    ) ON moo = me

Это интересно. Мы объединяем JOINс подзапросом. И здесь мы получаем реальную силу подзапросов. Представьте набор данных с миллионами строк, wilcoно только с несколькими отдельными me. Вместо того, чтобы объединяться с огромным столом, теперь у нас есть меньшая временная таблица, с которой можно соединиться. Это может привести к гораздо более быстрым запросам в зависимости от размера базы данных. Вы можете получить тот же эффект с помощью CREATE TEMPORARY TABLE ...и INSERT INTO ... SELECT ..., что может обеспечить лучшую читаемость для очень сложных запросов (но может заблокировать наборы данных на повторяющемся уровне изоляции для чтения).

Вложенные подзапросы

SELECT moo, bar
  FROM (
    SELECT moo, CONCAT(roger, wilco) AS bar
      FROM foo
      GROUP BY moo
      HAVING bar LIKE 'SpaceQ%'
  ) AS temp_foo
  ORDER BY bar

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

Вывод

Подзапросы не являются заменой для a, JOINи вы не должны использовать их таким образом (хотя это возможно). По моему скромному мнению, правильное использование подзапроса - это использование в качестве быстрой замены CREATE TEMPORARY TABLE .... Хороший подзапрос уменьшает набор данных так, как вы не можете выполнить в ONвыражении a JOIN. Если подзапрос имеет одно из ключевых слов GROUP BYили, DISTINCTи предпочтительно не находится в полях выбора или операторе where, то это может значительно повысить производительность.

Trendfischer
источник
3
Для Sub-queries in the Join-statement: (1) генерация производной таблицы из самого подзапроса может занять очень много времени. (2) полученная производная таблица не индексируется. только эти два могут значительно замедлить SQL.
JXC
@jxc Я могу говорить только за MySQL (1) Там это временная таблица, похожая на соединение. Время зависит от количества данных. Если вы не можете уменьшить данные с помощью подзапроса, используйте объединение. (2) Это правильно, это зависит от того, какой фактор вы можете уменьшить во временной таблице. У меня были случаи из реальной жизни, когда я мог уменьшить размер соединения с нескольких миллионов до нескольких сотен и сократить время запроса с нескольких секунд (при полном использовании индекса) до четверти секунды с помощью подзапроса.
Trendfischer
IMO: (1) такая временная таблица (производная таблица) не материализуется, поэтому каждый раз, когда вы запускаете SQL, временная таблица должна быть воссоздана, что может быть очень дорогостоящим и реальным узким местом (т.е. управлять группой на миллионы записей) (2) даже если вы можете уменьшить размер временной таблицы до 10записей, так как нет индекса, это все равно означает, что потенциально можно запросить в 9 раз больше записей данных, чем без временной таблицы при присоединении к другим таблицам. Кстати, у меня была эта проблема раньше с моей БД (MySQL), в моем случае, использование подзапроса в SELECT listможет быть намного быстрее.
JXC
@jxc Я не сомневаюсь, что есть много примеров, когда использование подзапроса менее оптимально. В качестве хорошей практики вы должны использовать EXPLAINзапрос перед оптимизацией. Со старым set profiling=1вы могли легко увидеть, является ли временное место узким местом. И даже для индекса требуется время обработки, B-Trees оптимизируют запросы для записей, но таблица из 10 записей может быть намного быстрее, чем индекс для миллионов записей. Но это зависит от множества факторов, таких как размеры и типы полей.
Trendfischer
1
Мне очень понравилось ваше объяснение. Спасибо.
unpairestgood
43

Прежде всего, чтобы сравнить два первых, вы должны различать запросы с подзапросами:

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

Для первого класса запросов хорошая СУБД будет рассматривать соединения и подзапросы как эквивалентные и создавать одинаковые планы запросов.

В эти дни даже MySQL делает это.

Тем не менее, иногда это не так, но это не означает, что объединения всегда будут выигрывать - у меня были случаи, когда использование подзапросов в MySQL улучшало производительность. (Например, если что-то мешает планировщику mysql правильно оценить стоимость, и если планировщик не видит вариант соединения и вариант подзапроса одинаковыми, то подзапросы могут превзойти объединения, форсируя определенный путь).

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

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

неразумность
источник
1
Можете ли вы привести пример запроса, написанного с использованием подзапросов, которые нельзя преобразовать в объединения (второй класс, как вы его называете)?
Захра
24

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

(хотя Марсело Кантос упоминает об этом)

Я приведу пример из курсов Стэнфорда Lagunita по SQL.

Студенческий стол

+------+--------+------+--------+
| sID  | sName  | GPA  | sizeHS |
+------+--------+------+--------+
|  123 | Amy    |  3.9 |   1000 |
|  234 | Bob    |  3.6 |   1500 |
|  345 | Craig  |  3.5 |    500 |
|  456 | Doris  |  3.9 |   1000 |
|  567 | Edward |  2.9 |   2000 |
|  678 | Fay    |  3.8 |    200 |
|  789 | Gary   |  3.4 |    800 |
|  987 | Helen  |  3.7 |    800 |
|  876 | Irene  |  3.9 |    400 |
|  765 | Jay    |  2.9 |   1500 |
|  654 | Amy    |  3.9 |   1000 |
|  543 | Craig  |  3.4 |   2000 |
+------+--------+------+--------+

Применить таблицу

(заявки, поданные в конкретные университеты и специальности)

+------+----------+----------------+----------+
| sID  | cName    | major          | decision |
+------+----------+----------------+----------+
|  123 | Stanford | CS             | Y        |
|  123 | Stanford | EE             | N        |
|  123 | Berkeley | CS             | Y        |
|  123 | Cornell  | EE             | Y        |
|  234 | Berkeley | biology        | N        |
|  345 | MIT      | bioengineering | Y        |
|  345 | Cornell  | bioengineering | N        |
|  345 | Cornell  | CS             | Y        |
|  345 | Cornell  | EE             | N        |
|  678 | Stanford | history        | Y        |
|  987 | Stanford | CS             | Y        |
|  987 | Berkeley | CS             | Y        |
|  876 | Stanford | CS             | N        |
|  876 | MIT      | biology        | Y        |
|  876 | MIT      | marine biology | N        |
|  765 | Stanford | history        | Y        |
|  765 | Cornell  | history        | N        |
|  765 | Cornell  | psychology     | Y        |
|  543 | MIT      | CS             | N        |
+------+----------+----------------+----------+

Давайте попробуем найти баллы GPA для студентов, которые подали заявление по CSспециальности (независимо от университета)

Использование подзапроса:

select GPA from Student where sID in (select sID from Apply where major = 'CS');

+------+
| GPA  |
+------+
|  3.9 |
|  3.5 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

Среднее значение для этого набора результатов:

select avg(GPA) from Student where sID in (select sID from Apply where major = 'CS');

+--------------------+
| avg(GPA)           |
+--------------------+
| 3.6800000000000006 |
+--------------------+

Используя соединение:

select GPA from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+------+
| GPA  |
+------+
|  3.9 |
|  3.9 |
|  3.5 |
|  3.7 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

среднее значение для этого набора результатов:

select avg(GPA) from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+-------------------+
| avg(GPA)          |
+-------------------+
| 3.714285714285714 |
+-------------------+

Очевидно, что вторая попытка дает ложные результаты в нашем случае использования, учитывая, что она подсчитывает дубликаты для вычисления среднего значения. Также очевидно, что использование distinctс оператором на основе соединения не устранит проблему, учитывая, что оно будет ошибочно удерживать одно из трех вхождений 3.9партитуры. Правильный случай заключается в учете ДВУХ (2) вхождений 3.9балла, учитывая, что у нас действительно ДВУХ (2) учеников с таким баллом, которые соответствуют нашим критериям запроса.

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

pkaramol
источник
Я думаю, что вы не можете использовать подзапрос здесь. Это не тот случай, когда вы можете использовать логически, но один дает неправильный ответ из-за его технической реализации. Это тот случай, когда вы НЕ МОЖЕТЕ использовать подзапрос, потому что учащийся, не принадлежащий к CS, может набрать 3,9 балла, что входит в список баллов IN. Контекст CS теряется при выполнении подзапроса, а это не то, что мы хотим логически. Так что это не хороший пример, где можно использовать любой из них. Использование подзапроса является концептуально / логически неправильным для этого варианта использования, даже если, к счастью, он дает правильный результат для другого набора данных.
Саураб Патил
22

Документация MSDN для SQL Server говорит

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

так что если вам нужно что-то вроде

select * from t1 where exists select * from t2 where t2.parent=t1.id

попробуйте вместо этого использовать соединение. В других случаях это не имеет значения.

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

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

Угур Гюмюшан
источник
9
Замена подзапросов функциями - очень плохая идея с точки зрения производительности в некоторых СУБД (например, Oracle), поэтому я рекомендую прямо противоположное - использовать подзапросы / объединения вместо функций везде, где это возможно.
Франк Шмитт
3
@FrankSchmitt, пожалуйста, поддержите ваш аргумент ссылками.
Угур Гюмюшан,
2
Есть также случаи, когда вы должны использовать подзапрос вместо объединения, даже если вы проверяете существование: если вы проверяете NOT EXISTS. A NOT EXISTSвыигрывает по LEFT OUTER JOIN разным причинам: предварительная производительность, отказоустойчивость (в случае столбцов с числами) и удобочитаемость. sqlperformance.com/2012/12/t-sql-queries/left-anti-semi-join
Тим
16

Запустите очень большую базу данных из старой Mambo CMS:

SELECT id, alias
FROM
  mos_categories
WHERE
  id IN (
    SELECT
      DISTINCT catid
    FROM mos_content
  );

0 секунд

SELECT
  DISTINCT mos_content.catid,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

~ 3 секунды

Объяснение показывает, что они проверяют одинаковое количество строк, но одна занимает 3 секунды, а одна почти мгновенная. Мораль истории? Если важна производительность (когда это не так?), Попробуйте несколько способов и посмотрите, какой из них самый быстрый.

А также...

SELECT
  DISTINCT mos_categories.id,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

0 секунд

Опять те же результаты, такое же количество проверенных строк. Я предполагаю, что DISTINCT mos_content.catid занимает гораздо больше времени, чем DISTINCT mos_categories.id.

Джейсон
источник
1
Я хотел бы узнать больше о том, что вы пытаетесь указать в последней строке: «Я предполагаю, что DISTINCT mos_content.catid требует гораздо больше времени, чем DISTINCT mos_categories.id». , Вы говорите, что идентификатор должен быть назван только, idа не назван как-то так catid? Попытка оптимизировать мой доступ к БД, и ваши знания могут помочь.
bool.dev
2
использование SQL IN в этом случае является плохой практикой и ничего не доказывает.
Угур Гюмюшан
15

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

Но в случае, если в таблице более 100 000 записей, лучшим результатом будет подзапрос.

У меня есть одна таблица, в которой 500 000 записей, которые я создал ниже запроса, и время его результата, как

SELECT * 
FROM crv.workorder_details wd 
inner join  crv.workorder wr on wr.workorder_id = wd.workorder_id;

Результат: 13,3 секунды

select * 
from crv.workorder_details 
where workorder_id in (select workorder_id from crv.workorder)

Результат: 1,65 секунды

Виджай Гаджера
источник
Я согласен, иногда ломается запрос, когда у вас есть миллион записей, вы не хотите использовать объединения, потому что они берутся навсегда. Скорее обрабатывайте это в коде, а карту в коде лучше.
user1735921
1
Свяжите ваши объединения не достаточно быстро, возможно, вы пропустили индекс. Query Analyzer может быть очень полезен при сравнении фактической производительности.
digital.aaron
Я согласен с Ajay Gajera, я видел это для себя.
user1735921
14
Как имеет смысл сравнивать производительность двух запросов, которые возвращают разные результаты?
Пол Шпигель
Да, это разные запросы, но они возвращают один и тот же результат
король нео
12

Подзапросы обычно используются для возврата одной строки в качестве атомарного значения, хотя они могут использоваться для сравнения значений с несколькими строками с помощью ключевого слова IN. Они допускаются практически в любой значимой точке инструкции SQL, включая список целей, предложение WHERE и т. Д. Простой подзапрос может быть использован в качестве условия поиска. Например, между парой таблиц:

   SELECT title FROM books WHERE author_id = (SELECT id FROM authors WHERE last_name = 'Bar' AND first_name = 'Foo');

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

   SELECT title FROM books WHERE author_id IN (SELECT id FROM authors WHERE last_name ~ '^[A-E]');

Это, очевидно, отличается от, скажем, LEFT-JOIN, где вы просто хотите объединить материал из таблиц A и B, даже если условие соединения не находит подходящей записи в таблице B и т. Д.

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

rkulla
источник
11

Версия MySQL: 5.5.28-0ubuntu0.12.04.2-log

У меня также сложилось впечатление, что JOIN всегда лучше, чем подзапрос в MySQL, но EXPLAIN - лучший способ сделать суждение. Вот пример, где подзапросы работают лучше, чем JOIN.

Вот мой запрос с 3 подзапросами:

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=43) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=55) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

ПОЯСНИТЕ показывает:

+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
| id | select_type        | table    | type   | possible_keys                                       | key          | key_len | ref                                             | rows | Extra                    |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
|  1 | PRIMARY            | vrl      | index  | PRIMARY                                             | moved_date   | 8       | NULL                                            |  200 | Using where              |
|  1 | PRIMARY            | l        | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  1 | PRIMARY            | vrlih    | eq_ref | PRIMARY                                             | PRIMARY      | 9       | ranker.vrl.list_id,ranker.vrl.ontology_id,const |    1 | Using where              |
|  1 | PRIMARY            | lbs      | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  4 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  3 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+

Тот же запрос с JOINs:

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
LEFT JOIN list_tag lt1 ON lt1.list_id = vrl.list_id AND lt1.tag_id = 43 
LEFT JOIN list_tag lt2 ON lt2.list_id = vrl.list_id AND lt2.tag_id = 55 
INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
AND lt1.list_id IS NULL AND lt2.tag_id IS NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

и вывод:

+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys                                       | key          | key_len | ref                                         | rows | Extra                                        |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | lt3   | ref    | list_tag_key,list_id,tag_id                         | tag_id       | 5       | const                                       | 2386 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | l     | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.lt3.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | vrlih | ref    | PRIMARY                                             | PRIMARY      | 4       | ranker.lt3.list_id                          |  103 | Using where                                  |
|  1 | SIMPLE      | vrl   | ref    | PRIMARY                                             | PRIMARY      | 8       | ranker.lt3.list_id,ranker.vrlih.ontology_id |   65 | Using where                                  |
|  1 | SIMPLE      | lt1   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index; Not exists         |
|  1 | SIMPLE      | lbs   | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | lt2   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index                     |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+

Сравнение rowsстолбца показывает разницу, и используется запрос с JOIN Using temporary; Using filesort.

Конечно, когда я выполняю оба запроса, первый выполняется за 0,02 секунды, второй не завершается даже через 1 минуту, поэтому EXPLAIN объяснил эти запросы правильно.

Если у меня нет INNER JOIN на list_tagстоле, т.е. если я удаляю

AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL  

из первого запроса и соответственно:

INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403

из второго запроса EXPLAIN возвращает одинаковое количество строк для обоих запросов, и оба эти запроса выполняются одинаково быстро.

Арун
источник
У меня похожая ситуация, но с большим количеством объединений, чем у вас, попробую один раз объяснить
pahnin
В Oracle или PostgreSQL я бы попробовал: И НЕ СУЩЕСТВУЕТ (ВЫБЕРИТЕ 1 ИЗ list_tag ГДЕ list_id = l.list_id И tag_id в (43, 55, 246403))
Дэвид Олдридж
11

Подзапросы имеют возможность вычислять функции агрегации на лету. Например, найдите минимальную цену книги и получите все книги, которые продаются по этой цене. 1) Использование подзапросов:

SELECT titles, price
FROM Books, Orders
WHERE price = 
(SELECT MIN(price)
 FROM Orders) AND (Books.ID=Orders.ID);

2) с помощью JOIN

SELECT MIN(price)
     FROM Orders;
-----------------
2.99

SELECT titles, price
FROM Books b
INNER JOIN  Orders o
ON b.ID = o.ID
WHERE o.price = 2.99;
Влад
источник
Другой случай: множественные GROUP BYs с разными таблицами: stackoverflow.com/questions/11415284/… Подзапросы кажутся строго более общими. Смотрите также MySQL man: dev.mysql.com/doc/refman/5.7/en/optimizing-subqueries.html | dev.mysql.com/doc/refman/5.7/ru/rewriting-subqueries.html
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
6
-1 Это вводит в заблуждение, так как вы используете подзапрос и объединяет в обоих примерах. То, что вы выдвинули подзапрос во второй запрос, чтобы определить минимальную цену заказа, не имеет никакого эффекта, поскольку база данных будет делать то же самое. Кроме того, вы не переписываете соединение, используя подзапрос; оба запроса используют соединение. Вы находитесь правильно , что подзапросы позволяют агрегатные функции, но этот пример не демонстрирует тот факт.
Дэвид Харкнесс
Я согласен с Дэвидом, и вы можете использовать группу, чтобы получить минимальную цену.
user1735921
9
  • Общее правило таково, что в большинстве случаев объединения выполняются быстрее (99%).
  • Чем больше таблиц данных, тем медленнее подзапросы .
  • Чем меньше таблиц данных имеют, что подзапросы имеют одинаковую скорость, присоединяется .
  • подзапросы проще, легче понять и легче читать.
  • Большинство веб-фреймворков и фреймворков приложений, а также их ORM и Active record генерируют запросы с подзапросами , потому что с подзапросами легче разделить ответственность, поддерживать код и т. Д.
  • Для небольших веб-сайтов или приложений подзапросы - это нормально, но для больших веб-сайтов и приложений вам часто приходится переписывать сгенерированные запросы, чтобы объединить запросы, особенно если запрос использует много подзапросов. в запросе.

Некоторые люди говорят, что «некоторые СУБД могут переписать подзапрос в объединение или в присоединение к подзапросу, когда он считает, что один из них быстрее другого», но это утверждение относится к простым случаям, конечно, не для сложных запросов с подзапросами, которые на самом деле вызывают проблемы с производительностью.

fico7489
источник
> но это утверждение относится к простым случаям. Я понимаю, что это либо простой случай, который может быть переписан в «JOIN» СУБД, либо это настолько сложный случай, что здесь подходят подзапросы. :-) Хороший вопрос по ОРМ. Я думаю, что это оказывает наибольшее влияние.
пилат
4

Разница видна только тогда, когда вторая объединяющая таблица имеет значительно больше данных, чем первичная таблица. У меня был опыт, как показано ниже ...

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

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

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

JPK
источник
3

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

Eunwoo Song
источник
1

Я просто думаю о той же проблеме, но я использую подзапрос в части FROM. Мне нужно подключиться и запросить из больших таблиц, в «ведомой» таблице содержится 28 миллионов записей, но результат всего 128, так что малый результат - большие данные! Я использую функцию MAX () на нем.

Во-первых, я использую LEFT JOIN, потому что я думаю, что это правильный путь, mysql может оптимизировать и т. Д. Во второй раз, просто для тестирования, я переписываю для дополнительного выбора против JOIN.

Время выполнения левого соединения: 1.12 с Время выполнения SUB-SELECT: 0.06 с

В 18 раз быстрее выбор, чем объединение! Просто в ад Чокито. Подвыбор выглядит ужасно, но результат ...

Кароли Сзабо
источник
-1

Если вы хотите ускорить ваш запрос с помощью соединения:

Для «внутреннего соединения / соединения» не используйте условие «вместо», вместо этого используйте его в состоянии «ВКЛ». Например:

     select id,name from table1 a  
   join table2 b on a.name=b.name
   where id='123'

 Try,

    select id,name from table1 a  
   join table2 b on a.name=b.name and a.id='123'

Для «левого / правого соединения», не используйте в состоянии «ON», потому что, если вы используете левое / правое соединение, оно получит все строки для любой таблицы. Так что, не используйте его в «On». Итак, попробуйте использовать условие «Где»

Сэм Рубен
источник
Это зависит от сервера SQL и сложности запроса. Многие реализации SQL оптимизируют простые запросы, подобные этим, для лучшей производительности. Возможно, приведите пример имени сервера и версии, где такое поведение улучшает ответ?
Trendfischer