MySQL 在 WHERE 语句中使用 OR 查询速度慢

MySQL query slow with OR in WHERE statement

我有一个 SQL 查询,它看起来很简单但是 运行 非常慢 ~4s:

SELECT tblbooks.*
FROM tblbooks LEFT JOIN
    tblauthorships ON tblbooks.book_id = tblauthorships.book_id
WHERE (tblbooks.added_by=3 OR tblauthorships.author_id=3)
GROUP BY tblbooks.book_id
ORDER BY tblbooks.book_id DESC
LIMIT 10

解释结果:

| id   | select_type | table          | type  | possible_keys     | key     | key_len | ref                    | rows | Extra       |
+------+-------------+----------------+-------+-------------------+---------+---------+------------------------+------+-------------+
|    1 | SIMPLE      | tblbooks       | index | fk_books__users_1 | PRIMARY | 62      | NULL                   |   10 | Using where |
|    1 | SIMPLE      | tblauthorships | ref   | book_id           | book_id | 62      | tblbooks.book_id       |    1 | Using where |
+------+-------------+----------------+-------+-------------------+---------+---------+------------------------+------+-------------+
2 rows in set (0.000 sec)

如果我 运行 在 WHERE 语句中单独对 OR 的每个部分进行上述查询,则两个查询 return 结果都不到 0.01s。

简化架构:

| Field         | Type                  | Null | Key | Default             | Extra          |
+---------------+-----------------------+------+-----+---------------------+----------------+
| id            | int(10) unsigned      | NO   | MUL | NULL                | auto_increment |
| book_id       | varchar(20)           | NO   | PRI | NULL                |                |
| added_by      | int(11) unsigned      | NO   | MUL | NULL                |                |
+---------------+-----------------------+------+-----+---------------------+----------------+
| Field         | Type             | Null | Key | Default             | Extra          |
+---------------+------------------+------+-----+---------------------+----------------+
| authorship_id | int(11) unsigned | NO   | PRI | NULL                | auto_increment |
| book_id       | varchar(20)      | NO   | MUL | NULL                |                |
| author_id     | int(11) unsigned | NO   | MUL | NULL                |                |
+---------------+------------------+------+-----+---------------------+----------------+

tblauthorships 中的 book_id 和 author_id 列都创建了索引。

谁能给我指出正确的方向?

注意:我知道 book_id varchar 问题。

我通常将索引类比为电话簿。它按姓氏排序,然后按名字排序。如果您按姓氏查找某个人,则可以高效地找到他们。如果您按姓氏和名字查找一个人,它也很有效。但是如果你只按名字查找一个人,书的排序顺序就无济于事,你必须费力地搜索每一页。

现在,如果您需要按姓氏或名字在电话簿中搜索某个人,会发生什么情况?

SELECT * FROM TelephoneBook WHERE last_name = 'Thomas' OR first_name = 'Thomas';

这与仅按名字搜索一样糟糕。由于与您搜索的名字匹配的所有条目都应包含在结果中,因此您必须全部找到它们。

结论:在 SQL 搜索中使用 OR 很难优化,因为 MySQL 在给定查询中每个 table 只能使用一个索引。

解决方案:使用两个查询并 UNION 它们:

SELECT * FROM TelephoneBook WHERE last_name = 'Thomas'
UNION
SELECT * FROM TelephoneBook WHERE first_name = 'Thomas';

两个单独的查询各自在各自的列上使用索引,然后两个查询的结果是统一的(默认情况下 UNION 消除重复)。

在您的情况下,您甚至不需要为其中一个查询进行连接:

(SELECT b.*
 FROM tblbooks AS b
 WHERE b.added_by=3)
UNION
(SELECT b.*
 FROM tblbooks AS b
 INNER JOIN tblauthorships AS a USING (book_id)
 WHERE a.author_id=3)
ORDER BY book_id DESC
LIMIT 10

到目前为止,这两个答案都不是很理想。既然他们都有UNIONLIMIT,让我进一步优化他们的答案:

( SELECT ...
    ORDER BY ...
    LIMIT 10
) UNION DISTINCT
( SELECT ...
    ORDER BY ...
    LIMIT 10
)
ORDER BY ...
LIMIT 10

这让每个 SELECT 都有机会优化 ORDER BYLIMIT,使它们更快。然后 UNION DISTINCT 去重。最后,前 10 个被剥离以形成结果集。

如果通过 OFFSET 进行分页,此优化将变得更加棘手。参见 http://mysql.rjweb.org/doc.php/index_cookbook_mysql#or

另外...您的 table 需要两个索引:

INDEX(added_by)
INDEX(author_id)

(请使用SHOW CREATE TABLE;它比DESCRIBE更具描述性。)