MySQL LEFT JOIN с использованием OR очень медленно - PullRequest
0 голосов
/ 16 марта 2020

пользователей таблицы имеет около 80 000 записей

друзей таблицы имеет около 900 000 записей

Есть 104 записи с именем name = 'verena'

этот запрос (точка запрос пропал, потому что он очень упрощен) очень медленный (> 20 секунд):

SELECT users.id FROM users
LEFT JOIN friends ON (
    users.id = friends.user_id OR
    users.id = friends.friend_id
)
WHERE users.firstname = 'verena';

Однако, если я удалю ИЛИ внутри JOIN, запрос будет мгновенным, поэтому либо:

SELECT users.id FROM users
LEFT JOIN friends ON (
    users.id = friends.user_id
)
WHERE users.firstname = 'verena';

возвращение результатов 1487 или

SELECT users.id FROM users
LEFT JOIN friends ON (
    users.id = friends.friend_id
)
WHERE users.firstname = 'verena';

возвращение результатов 2849 выполняется мгновенно (0,001 с)

Если я удалю все остальное и go прямо для

SELECT 1 FROM friends WHERE user_id = xxx OR friend_id = xxx

или

SELECT id FROM users WHERE firstname = 'verena';

эти запросы также являются мгновенными.

Установлены индексы для friends.friend_id, friends.user_id и users.firstname.

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

Мое единственное подозрение сейчас заключается в том, что MariaDB сначала присоединяется ко ВСЕМ пользователям с друзьями, а затем фильтрует WHERE. имя = 'verena', вместо желаемого поведения: сначала отфильтруйте firstname = 'verena', а затем объедините результаты с таблицей друзей, но даже тогда я не понимаю, почему удаление OR внутри условия JOIN могло бы сделать это быстро.

Я тестировал это на 2 разных машинах, одна из которых работает MariaDB 10.3.22 с кластером Galera, а другая - с MariaDB 10.4.12 без кластера Galera

По какой технической причине такой запрос в топе огромное замедление, и как мне это исправить, не разбивая SQL на несколько операторов?

Редактировать: Вот вывод EXPLAIN для него, говорящий, что он не использует индекс для таблицы друзей и просматривает через все записи, как правильно указано в комментарии Бармара:

+------+-------------+---------+------+-------------------+-----------+---------+-------+--------+------------------------------------------------+
| id   | select_type | table   | type | possible_keys     | key       | key_len | ref   | rows   | Extra                                          |
+------+-------------+---------+------+-------------------+-----------+---------+-------+--------+------------------------------------------------+
|    1 | SIMPLE      | users   | ref  | firstname         | firstname | 768     | const | 104    | Using where; Using index                       |
|    1 | SIMPLE      | friends | ALL  | user_id,friend_id | NULL      | NULL    | NULL  | 902853 | Range checked for each record (index map: 0x6) |
+------+-------------+---------+------+-------------------+-----------+---------+-------+--------+------------------------------------------------+

Есть ли способ заставить SQL использовать оба индекса или мне просто нужно принять это ограничение и обойти его, используя, например, предложение Бармара?

1 Ответ

1 голос
/ 16 марта 2020

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

Решение состоит в том, чтобы выполнить два быстрых запроса и объединить их с UNION.

SELECT users.id FROM users
LEFT JOIN friends ON (
    users.id = friends.user_id
)
WHERE users.firstname = 'verena';
UNION
SELECT users.id FROM users
LEFT JOIN friends ON (
    users.id = friends.friend_id
)
WHERE users.firstname = 'verena';
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...