【问题标题】:MySQL query slow with OR in WHERE statementMySQL 查询在 WHERE 语句中使用 OR 很慢
【发布时间】: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 问题。

【问题讨论】:

    标签: mysql performance query-optimization


    【解决方案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更具描述性。)

    【讨论】:

    • 不错的解决方案,瑞克。让我做一些测试,我会回复你的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-03-22
    • 1970-01-01
    • 2022-01-19
    • 1970-01-01
    • 2021-04-10
    • 1970-01-01
    相关资源
    最近更新 更多