Когда использовать STRAIGHT_JOIN с MySQL

88

У меня был довольно сложный запрос, с которым я работал, и его выполнение занимало 8 секунд. EXPLAIN показывал странный порядок таблиц, и не все мои индексы использовались даже с подсказкой FORCE INDEX. Я наткнулся на ключевое слово соединения STRAIGHT_JOIN и начал заменять им некоторые из моих ключевых слов INNER JOIN. Заметил значительное улучшение скорости. В конце концов я просто заменил все свои ключевые слова INNER JOIN на STRAIGHT_JOIN для этого запроса, и теперь он выполняется за 0,01 секунды.

У меня вопрос: когда вы используете STRAIGHT_JOIN, а когда - INNER JOIN? Есть ли причина не использовать STRAIGHT_JOIN, если вы пишете хорошие запросы?

Грег
источник

Ответы:

73

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

Я рекомендую оставить все запросы как обычные JOIN. Если вы обнаружите, что в одном запросе используется неоптимальный план запроса, я бы предложил сначала попытаться немного переписать или реструктурировать запрос, чтобы увидеть, выберет ли оптимизатор тогда лучший план запроса. Кроме того, по крайней мере для innodb, убедитесь, что статистика вашего индекса не только устарела ( ANALYZE TABLE ). Это может привести к тому, что оптимизатор выберет плохой план запроса. Подсказки оптимизатора обычно должны быть вашим последним средством.

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

Натан
источник
59
Этот ответ на самом деле не объясняет, когда использовать straight_join .
Pacerier,
23

Из ссылки MySQL JOIN :

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

Jjclarkson
источник
27
Спасибо, но я уже читал руководство по MySQL. Надеюсь на дальнейшее объяснение.
Грег,
20

Вот сценарий, который недавно возник на работе.

Рассмотрим три таблицы: A, B, C.

A имеет 3000 строк; B имеет 300000000 строк; а в C 2000 строк.

Определены внешние ключи: B (a_id), B (c_id).

Предположим, у вас есть запрос, который выглядит так:

select a.id, c.id
from a
join b on b.a_id = a.id
join c on c.id = b.c_id

По моему опыту, MySQL в этом случае может выбрать вариант C -> B -> A. C меньше, чем A, а B огромен, и все они равнозначны.

Проблема в том, что MySQL не обязательно учитывает размер пересечения между (C.id и B.c_id) и (A.id и B.a_id). Если соединение между B и C возвращает столько же строк, сколько B, то это очень плохой выбор; если бы, начиная с A, отфильтровал бы B до такого количества строк, как A, то это был бы гораздо лучший выбор. straight_joinможно использовать для принудительного выполнения этого порядка следующим образом:

select a.id, c.id
from a
straight_join b on b.a_id = a.id
join c on c.id = b.c_id

Теперь aнужно присоединиться к предыдущему b.

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

Хотя это зависит от статистики. Если распределение данных изменится, расчет может измениться. Это также зависит от деталей реализации механизма соединения.

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

Барри Келли
источник
Как бы вы использовали прямое соединение для решения проблемы?
Hannele
@Hannele straight_joinоценивает левую таблицу перед правой. Итак, если вы хотите продолжить A -> B -> Cв моем примере, первое joinключевое слово можно заменить на straight_join.
Барри Келли
Ах, аккуратно. Было бы полезно включить это в качестве примера в свой ответ :)
Ханнеле
18

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

IAdapter
источник
11

STRAIGHT_JOINс помощью этого предложения вы можете управлять JOINпорядком: какая таблица сканируется во внешнем цикле, а какая во внутреннем.

Митендра
источник
Что такое внешний цикл и внутренний цикл?
Istiaque Ahmed
Таблицы @IstiaqueAhmed объединены вложенными циклами (возьмите первую строку из таблицы A и таблицу перехода цикла B, затем возьмите вторую строку ... и т. Д. Здесь таблица A находится во внешнем цикле)
Бухгалтер م
6

Я расскажу, почему мне пришлось использовать STRAIGHT_JOIN:

  • Я имел производительность вопрос с запросом.
  • Упростив запрос, он стал значительно более эффективным.
  • Пытаясь выяснить, какая именно часть вызывает проблему, я просто не могла. (2 левых соединения вместе были медленными, и каждое было независимо быстрым)
  • Затем я выполнил EXPLAIN как с медленным, так и с быстрым запросом (добавьте одно из левых соединений)
  • Удивительно, но MySQL полностью изменил порядок JOIN между двумя запросами.

Поэтому я заставил одно из соединений быть direct_join, чтобы заставить предыдущее соединение быть прочитанным первым. Это помешало MySQL изменить порядок выполнения и работало как шарм!

Николас Тери
источник
2

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

-- table sales (45000000) rows
-- table stores (3) rows
SELECT whatever
FROM 
    sales 
    INNER JOIN stores ON sales.storeId = stores.id
ORDER BY sales.date, sales.id 
LIMIT 50;
-- there is an index on (date, id)

ЕСЛИ оптимизатор выберет stores первый удар, это вызовет, Using index; Using temporary; Using filesortпотому что

если ORDER BY или GROUP BY содержит столбцы из таблиц, отличных от первой таблицы в очереди на соединение, создается временная таблица.

источник

здесь оптимизатору нужна небольшая помощь, сказав ему salesсначала ударить, используя

sales STRAIGHT_JOIN stores
Бухгалтер م
источник
1
(Я приукрасил ваш ответ.)
Рик Джеймс,
2

Если концы запроса с ORDER BY... LIMIT..., это может быть оптимальным переформулировать запрос , чтобы обмануть оптимизатор в делать то , LIMIT прежде чемJOIN .

(Этот ответ не относится только к исходному вопросу STRAIGHT_JOINи не относится ко всем случаям STRAIGHT_JOIN.)

Начиная с примера @Accountant م , в большинстве ситуаций это должно работать быстрее. (И это позволяет избежать подсказок.)

SELECT  whatever
    FROM  ( SELECT id FROM sales
                ORDER BY  date, id
                LIMIT  50
          ) AS x
    JOIN  sales   ON sales.id = x.id
    JOIN  stores  ON sales.storeId = stores.id
    ORDER BY  sales.date, sales.id;

Примечания:

  • Сначала выбирается 50 идентификаторов. Это будет особенно быстро с INDEX(date, id).
  • Затем обратное присоединение к salesпозволяет получить только 50 «чего угодно», не таща их за временным столом.
  • поскольку подзапрос по определению неупорядочен, ORDER BYво внешнем запросе должен быть повторен. (Оптимизатор может найти способ избежать выполнения другой сортировки.)
  • Да, это сложнее. Но обычно это быстрее.

Я против использования хитов, потому что «даже если сегодня это быстрее, завтра может не быть».

Рик Джеймс
источник
0

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

  • В правильном порядке

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

  • Увеличение идентификатора на 1 нарушает порядок. Обратите внимание на поле «Дополнительно»

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

  • Использование direct_join устраняет проблему

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

Неправильный порядок выполняется примерно 65 секунд, а при использовании Straight_join выполняется за миллисекунды.

Рай
источник
-5
--use 120s, 18 million data
    explain SELECT DISTINCT d.taid
    FROM tvassist_recommend_list_everyday_diverse d, tvassist_taid_all t
    WHERE d.taid = t.taid
      AND t.client_version >= '21004007'
      AND t.utdid IS NOT NULL
      AND d.recommend_day = '20170403'
    LIMIT 0, 10000

--use 3.6s repalce by straight join
 explain SELECT DISTINCT d.taid
    FROM tvassist_recommend_list_everyday_diverse d
    STRAIGHT_JOIN 
      tvassist_taid_all t on d.taid = t.taid 
    WHERE 
     t.client_version >= '21004007'
       AND d.recommend_day = '20170403'

      AND t.utdid IS NOT NULL  
    LIMIT 0, 10000
lhs295988029
источник
3
Это не дает вам достаточно информации, чтобы понять, когда подходят прямые соединения.
Hannele