Почему UNION намного быстрее, чем LEFT JOIN с OR? - PullRequest
0 голосов
/ 01 марта 2019

У меня довольно сложный запрос, который я действительно хочу структурировать, используя LEFT JOIN без каких-либо операторов UNION, но он выполняется слишком медленно.Даже когда я упрощаю его, чтобы изолировать проблему, я не понимаю, почему один запрос должен выполняться намного быстрее.

Я использую версию MySQL: 5.6.36-82.1-log

Можно ли как-нибудь оптимизировать этот запрос без использования UNION?

select SQL_NO_CACHE distinct `locations`.* from `locations` 
left join `location_address` on `location_address`.`location_id` = `locations`.`id` 
left join `addresses` on `location_address`.`address_id` = `addresses`.`id` 
left join `cities` on `addresses`.`city_id` = `cities`.`id`
where `cities`.`name` = 'New York'
or `locations`.`description` like '%New York%'

Время выполнения: 13,422 секунды

Когда я разделяю это и использую UNION, это намного быстрее:

(select SQL_NO_CACHE distinct `locations`.* from `locations` 
left join `location_address` on `location_address`.`location_id` = `locations`.`id` 
left join `addresses` on `location_address`.`address_id` = `addresses`.`id` 
left join `cities` on `addresses`.`city_id` = `cities`.`id` 
where `cities`.`name` = 'New York')
union
(select distinct `locations`.* from `locations` 
left join `location_address` on `location_address`.`location_id` = `locations`.`id` 
left join `addresses` on `location_address`.`address_id` = `addresses`.`id` 
left join `cities` on `addresses`.`city_id` = `cities`.`id` 
where `locations`.`description` like '%New York%')

Время выполнения: 0,219 секунд

Если я изменю «левое соединение» на (внутреннее) «соединение», это будет намного быстрее (но пропускает местоположения без адреса):

select SQL_NO_CACHE distinct `locations`.* from `locations` 
join `location_address` on `location_address`.`location_id` = `locations`.`id` 
join `addresses` on `location_address`.`address_id` = `addresses`.`id` 
join `cities` on `addresses`.`city_id` = `cities`.`id`
where `cities`.`name` = 'New York'
or `locations`.`description` like '%New York%'

Время выполнения: 0,219 секунды

Кроме того, добавление условия cities. name в LEFT JOIN не помогает:

select SQL_NO_CACHE distinct `locations`.* from `locations` 
left join `location_address` on `location_address`.`location_id` = `locations`.`id` 
left join `addresses` on `location_address`.`address_id` = `addresses`.`id` 
left join `cities` on `addresses`.`city_id` = `cities`.`id` AND `cities`.`name` = 'New York'
where `cities`.`name` = 'New York'
or `locations`.`description` like '%New York%'

Время выполнения: 13,812 секунды

Записи в каждой таблице:

  • местоположения: ~ 5000 строк
  • location_address: ~ 4900 строк (~ 100 местоположений имеют 2 записи, ~ 200 местоположений имеют 0)
  • адреса: ~ 5500 строк (~ 600 адресов связаны с другими таблицами)
  • цитирует: ~ 30000 строк (с использованием полной базы данных городов США)

Поле id в каждой таблице является первичным индексом, а cities. name также является индексом.locations. index - это длинное текстовое поле.

Вот несколько примеров структуры и данных:

местоположений

+----+----------------------+
| id | description          |
+----+---------------------+
| 1  | Somewhere out there  |
+----+----------------------+
| 2  | In New York          |
+----+----------------------+
| 3  | Elsewhere            |
+----+----------------------+

адрес_адреса

+----+-------------+------------+
| id | location_id | address_id |
+----+-------------+------------+
| 1  | 1           | 1          |
+----+-------------+------------+
| 2  | 1           | 2          |
+----+-------------+------------+
| 3  | 3           | 3          |
+----+-------------+------------+

адреса

+----+---------+
| id | city_id |
+----+---------+
| 1  | 1       |
+----+---------+
| 2  | 2       |
+----+---------+
| 3  | 2       |
+----+---------+

города

+----+-----------+
| id | name      |
+----+-----------+
| 1  | New York  |
+----+-----------+
| 2  | Chicago   |
+----+-----------+
| 3  | Houston   |
+----+-----------+

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

Ответы [ 3 ]

0 голосов
/ 01 марта 2019

Вы можете написать запрос следующим образом:

select *
from
(
    Select <sql statement a>
    UNION
    Select <sql statement a>
) x
where x. <extra where clauses here>

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

0 голосов
/ 02 марта 2019

Мне удалось решить проблему, добавив индекс в сводную таблицу:

ALTER TABLE `location_address` ADD INDEX `location_id_index` (`location_id` ASC);

Время выполнения: 0,188 секунды

Это немного быстрее, чем при использовании метода UNION.

0 голосов
/ 01 марта 2019

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

Кстати, как работает эта версия?

select SQL_NO_CACHE l.*
from locations l
where exists (select 1
              from location_address la join
                   addresses a
                   on la.address_id = a.id join
                   cities c
                   on a.city_id = c.id
              where la.location_id = l.id and c.name = 'New York'
             ) or
     l.description like '%New York%';

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

Для повышения производительности можно использовать индексы location_address(location_id), addresses(id, city_id) и city(id, name).

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