MySQL使用OR左连接速度非常慢

MySQL使用OR左连接速度非常慢,mysql,performance,join,mariadb,left-join,Mysql,Performance,Join,Mariadb,Left Join,表用户有大约80000条记录 table friends有大约90万条记录 有104条记录的名字为“verena” 此查询(由于其非常简化,查询点消失)非常慢(>20秒): 但是,如果删除联接中的或,则查询是即时的,因此: SELECT users.id FROM users LEFT JOIN friends ON ( users.id = friends.user_id ) WHERE users.firstname = 'verena'; 返回1487个结果或 SELECT us

表用户有大约80000条记录

table friends有大约90万条记录

有104条记录的名字为“verena”

此查询(由于其非常简化,查询点消失)非常慢(>20秒):

但是,如果删除联接中的或,则查询是即时的,因此:

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.001s)

如果我把所有的东西都拿走,直接去

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

这些查询也是即时的

设置了friends.friends\u id、friends.user\u id和users.firstname的索引

我不明白为什么top查询速度很慢,而如果手动将其拆分并执行隔离的语句,那么一切都会很快

我现在唯一的怀疑是,MariaDB首先将所有用户与朋友联系起来,然后才过滤firstname='verena',而不是先过滤firstname='verena',然后将结果与朋友表联系起来,但即使这样,我也不明白为什么删除联接条件中的OR会使它变得很快

我在两台不同的机器上进行了测试,一台运行带Galera集群的MariaDB 10.3.22,另一台运行不带Galera集群的MariaDB 10.4.12

top查询速度如此之慢的技术原因是什么?如何在不将SQL拆分为多个语句的情况下解决这个问题

编辑: 下面是它的解释输出,告诉它没有对friends表使用任何索引,并按照Barmar评论中正确的说明扫描所有记录:

+------+-------------+---------+------+-------------------+-----------+---------+-------+--------+------------------------------------------------+
| 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同时使用这两个索引,或者我必须接受这个限制,并使用Barmar的建议来解决这个问题?

MySQL通常无法在使用
连接不同列时使用索引。在联接中,每个表只能使用一个索引,因此如果它使用
friends.user\u id
索引,则不会使用
friends.friends\u 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';

在MySQL上,
通常速度较慢,因为它不能被索引。通常最好是执行两个单独的查询,并将它们与
UNION
合并。您能详细说明一下吗?如果正在使用OR,为什么SQL不能处理索引?为什么它不首先在friends.user\u id上使用索引,然后在friends.friends\u id上使用索引?它只能在一个联接中匹配每个表的一个索引。因此,如果它使用
用户id
的索引,它必须对
朋友id
进行完整扫描,反之亦然。因此,它只是对所有内容进行了全面扫描。感谢您提供的解决方案,但我更感兴趣的是“为什么”而不是解决方案。虽然真正的SQL非常长,但对于UNION SELECT,将其加倍将不是一件很愉快的事,有没有更好的方法不必增加两倍的代码大小?也许您可以在子查询中进行这种联合,并在主查询中执行所有常见的操作。但除此之外,我没有建议,这是MySQL的设计限制。
+------+-------------+---------+------+-------------------+-----------+---------+-------+--------+------------------------------------------------+
| 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) |
+------+-------------+---------+------+-------------------+-----------+---------+-------+--------+------------------------------------------------+
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';