【问题标题】:Can you use index in mysql using "col1 OR col2"?你可以使用“col1 OR col2”在mysql中使用索引吗?
【发布时间】:2009-09-06 22:41:08
【问题描述】:

我有一个 mysql 查询,它获取用户是发送者或接收者的私人消息列表。

    SELECT 
    users_user1.user_name AS pm_username_1, 
    users_user1.user_avatar AS pm_username_1_avatar,
    users_user2.user_name AS pm_username_2,
    users_user2.user_avatar AS pm_username_2_avatar, 
    pms.*
FROM pm pms
LEFT JOIN users users_user1 
    ON users_user1.user_id = pms.pm_sender
LEFT JOIN users users_user2
    ON users_user2.user_id = pms.pm_receiver
WHERE pm_thread = pm_id 
    AND (pm_receiver = '1' OR pm_sender = '1')
    AND pm_delete != '1'
ORDER by pm_thread_last DESC LIMIT 0, 15

问题是……据我所知……它不能使用任何索引。

有什么办法可以解决这个问题?

编辑

+----+-------------+-------------+--------+---------------+---------+---------+------------------------+-------+-----------------------------+
| id | select_type | table       | type   | possible_keys | key     | key_len | ref                    | rows  | Extra                       |
+----+-------------+-------------+--------+---------------+---------+---------+------------------------+-------+-----------------------------+
|  1 | SIMPLE      | pms         | ALL    | pm_receiver   | NULL    | NULL    | NULL                   | 25354 | Using where; Using filesort |
|  1 | SIMPLE      | users_user1 | eq_ref | PRIMARY       | PRIMARY | 4       | movies.pms.pm_sender   |     1 |                             |
|  1 | SIMPLE      | users_user2 | eq_ref | PRIMARY       | PRIMARY | 4       | movies.pms.pm_receiver |     1 |                             |
+----+-------------+-------------+--------+---------------+---------+---------+------------------------+-------+-----------------------------+

将架构更改为:

(SELECT 
    users_user1.user_name AS pm_username_1, 
    users_user1.user_avatar AS pm_username_1_avatar,
    users_user2.user_name AS pm_username_2,
    users_user2.user_avatar AS pm_username_2_avatar, 
    pms.*
FROM pm pms
LEFT JOIN users users_user1 
    ON users_user1.user_id = pms.pm_sender
LEFT JOIN users users_user2
    ON users_user2.user_id = pms.pm_receiver
WHERE pm_thread = pm_id 
    AND (pm_receiver = '1')
    AND pm_delete != '1')
UNION
(SELECT 
    users_user1.user_name AS pm_username_1, 
    users_user1.user_avatar AS pm_username_1_avatar,
    users_user2.user_name AS pm_username_2,
    users_user2.user_avatar AS pm_username_2_avatar, 
    pms.*
FROM pm pms
LEFT JOIN users users_user1 
    ON users_user1.user_id = pms.pm_sender
LEFT JOIN users users_user2
    ON users_user2.user_id = pms.pm_receiver
WHERE pm_thread = pm_id 
    AND (pm_sender = '1')
    AND pm_delete != '1')
ORDER by pm_thread_last DESC LIMIT 0, 15

解释

+----+--------------+-------------+--------+---------------+-------------+---------+------------------------+------+----------------+
| id | select_type  | table       | type   | possible_keys | key         | key_len | ref                    | rows | Extra          |
+----+--------------+-------------+--------+---------------+-------------+---------+------------------------+------+----------------+
|  1 | PRIMARY      | pms         | ref    | pm_receiver   | pm_receiver | 4       | const                  |  336 | Using where    |
|  1 | PRIMARY      | users_user1 | eq_ref | PRIMARY       | PRIMARY     | 4       | movies.pms.pm_sender   |    1 |                |
|  1 | PRIMARY      | users_user2 | eq_ref | PRIMARY       | PRIMARY     | 4       | movies.pms.pm_receiver |    1 |                |
|  2 | UNION        | pms         | ref    | pm_sender     | pm_sender   | 4       | const                  |  283 | Using where    |
|  2 | UNION        | users_user1 | eq_ref | PRIMARY       | PRIMARY     | 4       | movies.pms.pm_sender   |    1 |                |
|  2 | UNION        | users_user2 | eq_ref | PRIMARY       | PRIMARY     | 4       | movies.pms.pm_receiver |    1 |                |
| NULL | UNION RESULT | <union1,2>  | ALL    | NULL          | NULL        | NULL    | NULL                   | NULL | Using filesort |
+----+--------------+-------------+--------+---------------+-------------+---------+------------------------+------+----------------+

【问题讨论】:

  • 在查询中发布 EXPLAIN 的输出。 (并不是说如果表很小,mysql 可能不会使用索引 - 扫描可能同样快。)
  • 嘿,你为什么把 1337 改成 1?抱怨:当您更改问题数据时,很难写出问题的答案。

标签: sql mysql indexing


【解决方案1】:

是的,MySQL 可以在 OR 表达式中使用索引。你怎么知道它没有使用你的索引,你有没有使用 EXPLAIN 来查看 MySQL 是如何运行你的查询的?您在该表中有多少行?如果行数太小,那么 MySQL 将不会使用索引来更快地进行全表扫描。我认为阈值是 100 - 如果一个表的行数少于 100 行,那么它总是会进行表扫描而不是使用索引。

【讨论】:

  • 该表的记录比这多得多。我确实使用了解释...。它没有使用带有 OR 表达式的索引。
【解决方案2】:

您可以通过索引提示强制解决问题,但这样做可能不会导致更好的查询性能。

http://dev.mysql.com/doc/refman/5.0/en/index-hints.html

您使用了哪些索引定义?

【讨论】:

    【解决方案3】:

    如果您考虑优化器想要做什么,它很难有效地利用所显示的查询。

    当优化器通过索引读取数据时,它会获取索引列的列值,以及包含这些值的行数以及在哪里可以找到这些行的信息。显然,对于唯一索引,“多少行”信息是 1。通常,还有一些方法可以为一组特定的行值查找索引条目(我认为是所有索引方法)。对于某些索引类型,有一种方法可以找到与索引的前导列(B 树索引和亲属)部分匹配的第一个索引条目。我将假设有关在何处查找行的信息存储为“rowid”;该术语在 DBMS 中并不完全统一,但可以使用。因此,索引条目通常标识键值和列保存键值的一组行。

    我建议忽略pm_thread = pm_id 标准,因为它看起来像一个连接标准。如果它实际上是查询中唯一表的两列之间的条件,那么它也是有问题的 - 不容易通过索引进行搜索。

    另外两个条件是:

    1. (pm_receiver = '1337' OR pm_sender = '1337')
    2. pm_delete != '1337'

    其中第二个通常是非常不具选择性的 - 不等于条件通常返回表中的几乎所有行,并且(单独)最好通过拒绝少数不匹配的行的表扫描来处理。这可能有例外,这就是优化器使用统计信息的原因。考虑加利福尼亚的一家小企业;如果它的大多数客户也在加利福尼亚州,那么条件 state != 'CA' 可能非常有选择性,如果有 30,000 个客户在 CA 和 20 个外部(但类似的条件 state != 'AZ' 是非常非选择性的;它甚至可能选择每一行从表中删除,但最多删除 20 行)。但是如果没有统计数据来证明这种逆向结论的合理性,优化器会假设不等于条件不是选择性的。

    这留下了第一个条件 - 两个不同列上的 OR 子句。个别标准可能具有相当的选择性;不会有很多行匹配pm_receiver = '1337',也不会有很多行匹配pm_sender = '1337'

    但是优化器如何使用索引来找到满足其中一个或其他条件的行呢?如果有两个索引可用,一个以pm_receiver 作为前导列,另一个以pm_sender 作为前导列,那么优化器可能会从第一个索引中读取行的“rowids”,以及“rowids” ' 对于来自第二个索引的行,然后取这些 rowid 的集合并集。然后它可以继续进行其余的查询处理。然而,使用这样的两个索引比表扫描更快并不能自动明确,并且许多优化器不这样做。他们会扫描表格,并依次评估每一行的条件。他们这样做通常是正确的 - 这是他们处理查询的最快方式。

    如果优化器尝试只使用其中一个索引 - 因为可能只有一个索引存在 - 那么它需要完成更复杂的工作。如果索引存在于(pm_receiver, pm_sender),那么它可以通过扫描整个索引来回答查询,查找pm_receiver 为“1337”或pm_sender 为“1337”的行。这是否是性能上的胜利取决于列的大小、表的大小和执行引擎的内部结构。大多数 DBMS 不会尝试这种策略,尤其是当它们无论如何都必须引用磁盘上的行来完成查询时。如果所有相关的列都包含在索引中,只扫描索引可能是一个成功的策略,但如果它也需要去磁盘获取数据,那么它可能不是一个成功的策略。

    (如果pm_thread = pm_id 标准是单行中的列之间的条件,则它也不能通过索引评估,除非索引包含两列,并且还需要完整的索引扫描才能找到条件适用的行. 如果可能的话,优化器会更喜欢在 OR 条件上使用索引,因为它会有更好的选择性。)

    因此,给定一个具有按行存储的表(不是列式数据库)和普通索引的普通 DBMS,优化器没有一种简单的方法可以有效地使用索引来回答查询 - 所以优化器选择不麻烦。


    在输入上述内容时,已修改问题以显示具有两个 LEFT OUTER JOIN (LOJ) 条件的多路连接。

    LOJ 本身就是性能杀手。应尽可能避免。这些的存在使得使用索引变得更加困难。我们需要知道所涉及的每个表的完整模式,包括表上的索引。即便如此,优化器可能会扫描主表(其他表与其他表保持外连接)并使用索引查找在外连接表中查找匹配行 - 或不存在匹配行。

    【讨论】:

      【解决方案4】:

      确实,因为它是一个 OR 条件,所以 MySQL 不能在提到的任何一个列上使用任何索引。那是因为索引允许您按一列或另一列搜索,但不能同时搜索。

      我建议将查询拆分为两个查询,这样您就不必使用 OR。在此之前 - 检查这是否真的会给你带来性能问题。也许您正在尝试解决错误的问题。


      补充:看到完整的查询后,我只能说 - 重新考虑您的数据结构。这对于数据完整性或其他方面可能非常有用,但如果没有全表扫描,您就无法编写这样的查询。如果您无法对其进行重组,则可以添加另一个缓存了必要信息的表。不过,您必须使缓存保持最新状态。

      【讨论】:

        猜你喜欢
        • 2016-08-12
        • 2021-01-31
        • 1970-01-01
        • 1970-01-01
        • 2016-10-10
        • 2018-10-13
        • 1970-01-01
        • 2010-12-14
        • 2022-10-23
        相关资源
        最近更新 更多