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】:到目前为止,这两个答案都不是很理想。既然他们同时拥有UNION
和LIMIT
,让我进一步优化他们的答案:
( SELECT ...
ORDER BY ...
LIMIT 10
) UNION DISTINCT
( SELECT ...
ORDER BY ...
LIMIT 10
)
ORDER BY ...
LIMIT 10
这使每个SELECT
都有机会优化ORDER BY
和LIMIT
,使其更快。然后是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 很慢的主要内容,如果未能解决你的问题,请参考以下文章