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。
简化架构:
- 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(< 100 行):
| 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
到目前为止,这两个答案都不是很理想。既然他们都有UNION
和LIMIT
,让我进一步优化他们的答案:
( SELECT ...
ORDER BY ...
LIMIT 10
) UNION DISTINCT
( SELECT ...
ORDER BY ...
LIMIT 10
)
ORDER BY ...
LIMIT 10
这让每个 SELECT
都有机会优化 ORDER BY
和 LIMIT
,使它们更快。然后 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
更具描述性。)
我有一个 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。
简化架构:
- 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(< 100 行):
| 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
到目前为止,这两个答案都不是很理想。既然他们都有UNION
和LIMIT
,让我进一步优化他们的答案:
( SELECT ...
ORDER BY ...
LIMIT 10
) UNION DISTINCT
( SELECT ...
ORDER BY ...
LIMIT 10
)
ORDER BY ...
LIMIT 10
这让每个 SELECT
都有机会优化 ORDER BY
和 LIMIT
,使它们更快。然后 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
更具描述性。)