【问题标题】:Can MySQL use Indexes when there is OR between conditions?当条件之间存在 OR 时,MySQL 可以使用索引吗?
【发布时间】:2016-10-10 07:53:56
【问题描述】:

我有两个查询加上它自己的EXPLAIN 的结果:

一个:

SELECT * 
FROM notifications 
WHERE id = 5204 OR seen = 3

基准 (10,000 行):0.861


两个:

SELECT h.* FROM ((SELECT n.* from notifications n WHERE id = 5204) 
                    UNION ALL
                 (SELECT n.* from notifications n WHERE seen = 3)) h 

基准(10,000 行):2.064


上面两个查询的结果是一样的。我在notifications 表上也有这两个索引:

notifications(id) -- this is PK
notification(seen)

如您所知,OR 通常会阻止有效使用索引,这就是我编写第二个查询的原因(UNION)。但经过一些测试后,我发现仍然使用OR 比使用UNION 快得多。所以我很困惑,我真的无法选择最好的选择。

基于一些合乎逻辑和合理的解释,使用union 更好,但基准测试结果显示使用OR 更好。请问我应该使用哪种方法?

【问题讨论】:

  • 为什么要使用选择(选择联合选择)而不是使用更简单的选择联合选择?您还需要一个 UNION 而不是 UNION ALL 以避免重复
  • 虽然我不知道答案——我也发现 OR 语句会影响 MySQL 的性能。通常我会更改一些内容,例如 select a.* from account a join account b on a.member_number = b.member_number 或 a.last_name = b.last_name。 --> 选择 a.* from account a left join account b on a.member_number left join account c on a.last_name = c.last_name where b.account_id is not null or c.account_id is not null;绕过 OR 条件的糟糕表现。我很想知道真正的答案.....
  • 如果您可以在第二个示例中删除 SELECT h.* FROM ( ) h 并看看它是否有所作为会很好
  • @RolandStarke 说了什么。来自文档:在 MySQL 5.7.6 之前,派生表总是被物化,而等效的视图引用有时被物化,有时被合并。这种对等价查询的不一致处理可能会导致性能问题:不必要的派生表实现需要时间并阻止优化器将条件下推到派生表。
  • @Stack: UNIONUNION ALL? UNION 必须确保结果是唯一的,这就是为什么它可能会抛出另一个临时表。尝试将id <> 5204 添加到您的seen 查询中,看看UNION ALL 的执行情况。

标签: mysql sql


【解决方案1】:

OR 案例的查询计划似乎表明 MySQL 确实在使用索引,所以显然是的,它可以做到,至少在这种情况下。这似乎完全合理,因为seen 上有一个索引,而id 是PK。

基于一些合乎逻辑和合理的解释,使用联合更好,但基准测试的结果说使用 OR 更好。

如果“逻辑合理的解释”与现实相矛盾,那么可以肯定地认为逻辑有缺陷,或者解释是错误的或不适用的。众所周知,性能难以预测。在速度很重要的情况下,性能测试是必不可少的。

请问我应该使用哪种方法?

您应该使用在输入上测试更快的那个,以充分模拟程序在实际使用中看到的内容。

另外请注意,您的两个查询在语义上并不等效:如果带有id = 5204 的行也有seen = 3,那么OR 查询将返回一次,但UNION ALL 查询将返回两次.除了哪个是正确的,在正确代码和错误代码之间进行选择是没有意义的。

【讨论】:

  • 我明白了。也是的,你是对的,如果行 id = 5204seen = 3 那么结果会有所不同。但是我用UNION 替换了UNION ALL,现在结果是相同的。所以总的来说,你认为我必须选择OR,对吗?
  • 这里,这里。当您的基准测试产生矛盾的结果时,世界上所有的理论都无关紧要。
  • 如果您提供的OR 查询是产生正确结果的查询,那么UNION ALL 查询就是错误的。它不是真正的替代品。令人高兴的是,OR 查询似乎有一个非常有效的查询计划,并且它在您的测试中也表现得更好。我不明白为什么会有任何不确定的选择。
  • 你知道吗?!存在不确定性,因为 Gordon (数据库科目中的完美人) suggests me 使用 UNION 而不是 OR ..!您仍然认为我应该使用 OR 吗? :-)
  • @Stack,Gordon 知识渊博,但在您链接的答案中,他建议您尝试 UNION 子查询以查看是否是比较快的。他没有断言UNION 会更快。实际上,他所说的与我所做的完全一样:依靠性能测试。
【解决方案2】:

答案包含在您的问题中。 OR 的 EXPLAIN 输出显示 Using union(PRIMARY, seen) - 这意味着正在使用 index_merge 优化,并且查询实际上是通过合并两个索引的结果来执行的。

所以 MySQL 在某些情况下可以使用索引,在这个情况下也是如此。但是index_merge 并不总是可用或不使用,因为索引的统计数据表明它不值得。在这些情况下 OR 可能比 UNION 慢很多(或者不是,如果您不确定,则需要始终检查这两个版本)。

在您的测试中,您“走运了”,MySQL 自动为您进行了正确的优化。并非总是如此。

【讨论】:

    【解决方案3】:

    index_merge,顾名思义,使用Sort Merge JoinSort Merge UnionANDOR 条件适当地组合两个索引的主键,然后查找其余的值PK表。

    为此,两个索引上的条件应该是每个索引都会按顺序产生主键(您的条件是)。

    您可以在docs 中找到条件的严格定义,但简而言之,您应该使用相等条件过滤索引的所有部分,可能还包括<= 或@987654329 @上PK。

    如果您在(col1, col2, col3) 上有索引,则应该是col1 = :val1 AND col2 = :val2 AND col3 = :val3 [ AND id > :id ](方括号中的部分不是必需的)。

    以下条件无效:

    col1 = :val1 -- you omit col2 and col3
    
    col1 = :val1 AND col2 = :val2 AND col3 > :val3 -- you can only use equality on key parts
    

    作为一个免费的副作用,您的输出按id 排序。

    您可以使用以下方法获得类似的结果:

    SELECT  *
    FROM    (
            SELECT  5204 id
            UNION ALL
            SELECT  id
            FROM    mytable
            WHERE   seen = 3
                    AND id <> 5204
            ) q
    JOIN    mytable m
    ON      m.id = q.id
    

    ,除了在早期版本的 MySQL 中,派生表必须物化,这肯定会使查询性能变差,并且您的结果将不再按id 排序。

    简而言之,如果您的查询允许index_merge(union),那就去吧。

    【讨论】:

    • 很好的解释..!赞成。请您告诉我index_merge 的确切时间是什么时候?当一部分条件是PK时?
    • +1 .. 你确定吗?我猜这个查询. . . WHERE col1 = :val1 会利用这个索引(col1, col2, col3) .. 因为col1 是那个索引的第一个。
    • @Shafizadeh:不在index_merge 中,它不会。 PK 没有按此条件排序。
    • @Quassnoi 我明白了。你的意思是在index_merge 中只使用单列索引(不是多列索引)
    • @Shafizadeh:不,这不好。您只能在 PK 上使用范围条件,以使条件符合index_merge 的条件。您提供的链接不包括index_merge。该索引确实可以用于范围访问,只是范围访问不会以 PK 顺序返回元组。我的答案中有一个文档链接,请随时阅读。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-02-17
    • 1970-01-01
    • 1970-01-01
    • 2012-12-21
    • 1970-01-01
    • 2012-10-24
    • 1970-01-01
    相关资源
    最近更新 更多