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

Posted

技术标签:

【中文标题】MySQL 查询在 WHERE 语句中使用 OR 很慢【英文标题】:MySQL query slow with OR in WHERE statement 【发布时间】:2021-07-12 15:02:59 【问题描述】:

我有一个 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 的每个部分单独运行上述查询,则两个查询都在不到 0.01 秒内返回结果。

简化架构:

tblbooks(约 100 万行):
| 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                |                |
+---------------+-----------------------+------+-----+---------------------+----------------+
tblauthorships(
| 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 问题。

【问题讨论】:

【参考方案1】:

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

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

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

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

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

解决方案:使用两个查询并将它们联合起来:

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

【讨论】:

我现在正在读你的书(数据库编程的陷阱),从第一句话开始我就想“我知道这是谁” 我喜欢你的比喻。这很容易理解。 UNION 确实出现在我的脑海中。我想要的也是应用分页。当UNION这样的时候,它不会在应用LIMIT 10之前将added_by=3的tblbooks和author_id=3的tblauthorships中的所有记录都选择到内存中吗? 是的,UNION 通常确实使用临时表来保存累积的结果集,然后将 LIMIT 应用于临时表。感谢您阅读我的书!【参考方案2】:

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

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

这使每个SELECT 都有机会优化ORDER BYLIMIT,使其更快。然后是UNION DISTINCT dedups。最后,将前 10 个剥离出来形成结果集。

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

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

INDEX(added_by)
INDEX(author_id)

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

【讨论】:

不错的解决方案,瑞克。让我做一些测试,我会回复你的。

以上是关于MySQL 查询在 WHERE 语句中使用 OR 很慢的主要内容,如果未能解决你的问题,请参考以下文章

Python 操作Redis

python爬虫入门----- 阿里巴巴供应商爬虫

Python词典设置默认值小技巧

《python学习手册(第4版)》pdf

Django settings.py 的media路径设置

Python中的赋值,浅拷贝和深拷贝的区别