Left Join превосходит Inner Join? - PullRequest
15 голосов
/ 09 октября 2008

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

Изменение ЛЕВОГО СОЕДИНЕНИЯ на ВНУТРЕННЕЕ СОЕДИНЕНИЕ обрезало результирующий набор до того, что было необходимо, и, вероятно, также было бы более производительным (так как выбрано меньше строк). На самом деле, запрос LEFT JOIN превосходил INNER JOIN, занимая половину времени.

LEFT JOIN: (всего 127 строк, запрос занял 0,0011 с)

ВНУТРЕННЕЕ СОЕДИНЕНИЕ: (всего 10 строк, запрос занял 0,0024 с)

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

Запуск EXPLAIN на обоих не показывает ничего, что объясняет различия в производительности:

ДЛЯ ВНУТРЕННЕГО СОЕДИНЕНИЯ:

id  select_type     table   type    possible_keys   key     key_len     ref        rows     Extra
1   SIMPLE  contacts        index       NULL        name        302     NULL         235    Using where
1   SIMPLE  lists           eq_ref      PRIMARY     PRIMARY     4   contacts.list_id     1   
1   SIMPLE  lists_to_users  eq_ref      PRIMARY     PRIMARY     8   lists.id,const  1    
1   SIMPLE  tags            eq_ref      PRIMARY     PRIMARY     4   lists_to_users.tag_id   1    
1   SIMPLE  users           eq_ref      email_2     email_2     302     contacts.email 1    Using where

Для левого соединения:

id  select_type     table   type    possible_keys   key     key_len     ref     rows    Extra
1   SIMPLE          contacts index      NULL        name        302     NULL    235     Using where
1   SIMPLE        lists     eq_ref      PRIMARY     PRIMARY     4   contacts.list_id    1    
1   SIMPLE    lists_to_users eq_ref     PRIMARY     PRIMARY     8   lists.id,const  1    
1   SIMPLE         tags     eq_ref      PRIMARY     PRIMARY     4   lists_to_users.tag_id   1    
1   SIMPLE        users     eq_ref      email_2     email_2     302     contacts.email  1   

И сам запрос:

SELECT `contacts`.*, `lists`.`name` AS `group`, `lists`.`id` AS `group_id`, `lists`.`shared_yn`, `tags`.`name` AS `context`, `tags`.`id` AS `context_id`, `tags`.`color` AS `context_color`, `users`.`id` AS `user_id`, `users`.`avatar` 
FROM `contacts`  
LEFT JOIN `lists` ON lists.id=contacts.list_id  
LEFT JOIN `lists_to_users` ON lists_to_users.list_id=lists.id AND lists_to_users.user_id='1' AND lists_to_users.creator='1'  
LEFT JOIN `tags` ON tags.id=lists_to_users.tag_id 
INNER JOIN `users` ON users.email=contacts.email 
WHERE (contacts.user_id='1') 
ORDER BY `contacts`.`name` ASC

(пункт, о котором я говорю, является последним INNER JOIN в таблице 'users')

Запрос выполняется в базе данных MySQL 5.1, если он имеет значение.

Кто-нибудь знает, почему запрос LEFT JOIN превосходит INNER JOIN в этом случае?

ОБНОВЛЕНИЕ: Из-за предположения Томалака, что маленькие таблицы, которые я использую, усложняли INNER JOIN, я создал тестовую базу данных с некоторыми фиктивными данными. Таблица 'users' содержит 5000 строк, а таблица контактов ~ 500 000 строк. Результаты одинаковы (также время не изменилось, что удивительно, если учесть, что таблицы теперь намного больше).

Я также запустил ANALYZE и OPTIMIZE в таблице контактов. Ничего не заметно.

Ответы [ 6 ]

12 голосов
/ 09 октября 2008

Если вы думаете, что реализация LEFT JOIN - это INNER JOIN + больше работы, то этот результат сбивает с толку. Что если реализация INNER JOIN - это (LEFT JOIN + фильтрация)? Ах, теперь ясно.

В планах запросов единственное отличие заключается в следующем: users ... extra: using where . Это означает фильтрацию. В запросе с внутренним объединением есть дополнительный шаг фильтрации .


Это другой вид фильтрации, чем обычно используемый в предложении where. Создать индекс A для поддержки этого действия фильтрации просто.

SELECT *
FROM A
WHERE A.ID = 3

Рассмотрим этот запрос:

SELECT *
FROM A
  LEFT JOIN B
  ON A.ID = B.ID
WHERE B.ID is not null

Этот запрос эквивалентен внутреннему объединению. На B нет индекса, который бы помог этому фильтрующему действию. Причина в том, что в предложении where указывается условие результата объединения вместо условия для B.

6 голосов
/ 09 октября 2008

Вероятно, это связано с тем, что ВНУТРЕННЕМУ СОЕДИНЕНИЮ необходимо проверить каждую строку в обеих таблицах, чтобы увидеть, совпадают ли значения столбцов (в вашем случае это адрес электронной почты). LEFT JOIN вернет все из одной таблицы независимо. Если он проиндексирован, он будет знать, что делать быстрее.

4 голосов
/ 09 октября 2008

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

2 голосов
/ 09 октября 2008

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

0 голосов
/ 22 апреля 2009

Попробуйте это:

SELECT `contacts`.*, `lists`.`name` AS `group`, `lists`.`id` AS `group_id`, `lists`.`shared_yn`, `tags`.`name` AS `context`, `tags`.`id` AS `context_id`, `tags`.`color` AS `context_color`, `users`.`id` AS `user_id`, `users`.`avatar` 
FROM `contacts`  
INNER JOIN `users` ON contacts.user_id='1' AND users.email=contacts.email
LEFT JOIN `lists` ON lists.id=contacts.list_id  
LEFT JOIN `lists_to_users` ON lists_to_users.user_id='1' AND lists_to_users.creator='1' AND lists_to_users.list_id=lists.id
LEFT JOIN `tags` ON tags.id=lists_to_users.tag_id 
ORDER BY `contacts`.`name` ASC

Это должно дать вам дополнительную производительность, потому что:

  • Вы помещаете все внутренние объединения до появления любого "левого" или "правого" соединения. Это отфильтровывает некоторые записи перед применением последующих внешних объединений
  • Короткое замыкание операторов «И» (порядок «И» имеет значение). Если сравнение между столбцами и литералами имеет значение false, оно не выполнит необходимое сканирование таблицы для сравнения между таблицами PK и FKs

Если вы не обнаружили каких-либо улучшений производительности, замените весь набор столбцов на «COUNT (*)» и проведите левый / внутренний тесты. Таким образом, независимо от запроса, вы получите только одну строку с одним столбцом (количество), поэтому вы можете отказаться от того, что количество возвращаемых байтов является причиной медленного запроса:

SELECT COUNT(*)
FROM `contacts`  
INNER JOIN `users` ON contacts.user_id='1' AND users.email=contacts.email
LEFT JOIN `lists` ON lists.id=contacts.list_id  
LEFT JOIN `lists_to_users` ON lists_to_users.user_id='1' AND lists_to_users.creator='1' AND lists_to_users.list_id=lists.id
LEFT JOIN `tags` ON tags.id=lists_to_users.tag_id 

Удачи

0 голосов
/ 09 октября 2008

LEFT JOIN возвращает больше строк, чем INNER JOIN, потому что эти 2 разные.
Если LEFT JOIN не найдет связанную запись в таблице, которую он ищет, он вернет NULL для таблицы.
Но если INNER JOIN не найдет связанную запись, он вообще не вернет строку whole .

Но, к вашему вопросу, у вас включен query_cache? Попробуйте выполнить запрос с

SELECT SQL_NO_CACHE `contacts`.*, ...

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

ANALYZE TABLE t1, t2;
OPTIMIZE TABLE t1, t2;

И посмотри, что получится.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...