MySQL LEFT JOIN using OR is very slow

noother :

table users has about 80,000 records

table friends has about 900,000 records

There are 104 records with firstname = 'verena'

this query (the point of the query is gone because its very simplified) is very slow (> 20 seconds):

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

However, if I remove the OR inside the JOIN, the query is instant, so either:

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

returning 1487 results or

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

returning 2849 results execute instantly (0.001s)

If I remove everything else and go straight for

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

or

SELECT id FROM users WHERE firstname = 'verena';

these queries are also instant.

Indexes for friends.friend_id, friends.user_id and users.firstname are set.

I don't understand why the top query is slow while if manually taking it apart and executing the statements isolated everything is blazing fast.

My only suspicion now is that MariaDB is first joining ALL users with friends and only after that filters for WHERE firstname = 'verena', instead of the wanted behavior of first filtering for firstname = 'verena' and then joining the results with the friends table, but even then I don't see why removing the OR inside the JOIN condition would make it fast.

I tested this on 2 different machines, one running MariaDB 10.3.22 with Galera cluster and one with MariaDB 10.4.12 without Galera cluster

What is the technical reason why the top query is having such a huge slowdown and how do I fix this without having to split the SQL into several statements?

Edit: Here is the EXPLAIN output for it, telling it's not using any index for the friends table and scanning through all records as correctly stated in Barmar's comment:

+------+-------------+---------+------+-------------------+-----------+---------+-------+--------+------------------------------------------------+
| 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) |
+------+-------------+---------+------+-------------------+-----------+---------+-------+--------+------------------------------------------------+

Is there any way to make SQL use both indexes or do I just have to accept this limitation and work around it using for example Barmar's suggestion?

Barmar :

MySQL is not usually able to use an index when you use OR to join with different columns. It can only use one index per table in a join, so if it uses the friends.user_id index, it won't use friends.friend_id, and vice versa.

The solution is to do the two fast queries and combine them with 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';

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=319636&siteId=1