为什么 - 或者什么时候 - MySQL 不为 OR 条件使用索引,如果它用于 AND 条件?

Why - or when - doesn't MySQL use indexes for OR conditions, if it does for AND conditions?

我有一个 table the_table 属性 the_table.id, the_table.firstValthe_table.secondVal (当然主键是 the_table.id ).

像这样在第一个非键属性上定义索引后:

CREATE INDEX idx_firstval  
ON the_table (firstVal);

以下析取 (OR) 查询的 EXPLAIN 结果

SELECT * FROM the_table WHERE the_table.firstVal = 'A' OR the_table.secondVal = 'B';

| id    | select_type | table     | type    | possible_keys | key   | key_len   | ref   | rows  | Extra
| 1     | SIMPLE      | the_table | ALL     | idx_firstval  | NULL  | NULL      | NULL  | 3436  | Using where

这表明未使用索引 idx_firstval。现在,以下联合 (AND) 查询的 EXPLAIN 结果

SELECT * FROM the_table WHERE the_table.firstVal = 'A' AND the_table.secondVal = 'B';

| id    | select_type   | table     | type  | possible_keys | key           | key_len   | ref   | rows  | Extra 
| 1     | SIMPLE        | the_table | ref   | idx_firstval  | idx_firstval  | 767       | const | 124   | Using index condition; Using where

这次显示正在使用的索引。

为什么 MySQL 选择不为析取查询使用索引,而是为联合查询使用索引?

我已经搜索过了,正如 this thread、"using OR in a query will often cause the Query Optimizer to abandon use of index seeks and revert to scans" 中的答案所建议的那样。然而,这并没有回答为什么会发生,只是

Another thread 试图回答为什么析取查询不使用索引,但我认为这样做失败了——它只是得出结论,OP 使用的是一个小型数据库。我想知道 分离式和连接式的区别。

我很惊讶 MySQL 正在为两个查询中的任何一个使用索引。此处使用的正确索引是复合索引,它涵盖 WHERE 子句中的两列:

CREATE INDEX idx ON the_table (firstVal, secondVal);

至于为什么 MySQL 在第二种情况下使用索引,一种可能是如果 the_table 中的大多数记录具有 firstVal 值,即 不是A。在这种情况下,只要知道等式 the_table.firstVal = 'A' 为假就意味着 WHERE 子句的整个结果都是已知的(为假)。因此,关于为什么使用索引的答案可能与您的确切数据的 基数 有关。但无论如何,考虑使用复合索引覆盖所有基地。

因为MySQL执行计划只使用一个索引table。

如果 MySQL 在 idx_firstval 上使用范围扫描来满足 firstVal 列上的相等谓词,那么 MySQL 仍然需要检查 [=13= 上的条件]栏目。


AND相比,MySQL只需要检查索引范围扫描得到的行return。需要检查的行集受条件约束。


对于OR,MySQL需要检查索引范围扫描未return的行,[=42=中的所有其他行].没有索引,这意味着 table 的完整扫描。如果我们对 table 进行全面扫描以检查 secondVal,那么检查扫描中的两个条件(即包括索引访问和全面扫描会更贵。)

(如果有一个同时包含firstVal和secondVal的复合索引可用,那么对于OR查询,优化器可能认为检查table中的所有行成本更低通过执行完整索引扫描,然后查找数据页。)


当我们了解优化器可以使用哪些操作时,这会引导我们避免 OR 并重写查询,return 一个等效的结果集,其查询模式更明确定义两个集合的组合

SELECT a.*
  FROM the_table a
 WHERE a.firstVal = 'A'

UNION ALL

SELECT b.*
  FROM the_table b
 WHERE b.secondVal = 'B'
   AND NOT ( b.firstVal <=> 'A' )

(如果我们希望按特定顺序 return 编辑行,请添加 ORDER BY)