索引合并

MySQL 引入了一种叫“索引合并(index merge)的策略,它在一定程度上可以使用表中的多个单列索引来定位指定的行。在这种情况下,查询能够同时使用两个单列索引进行扫描,并将结果进行合并。这种算法有三个变种:

  • OR 条件的联合(union)

  • AND 条件的相交(intersection)

  • 组合前两种情况的联合及相交

下面的查询就使用了两个索引扫描的联合,通过 EXPLAIN 中的 Extra 列可以看到这点:

EXPLAIN
SELECT *
FROM film
WHERE film_id = 1
   OR language_id = 1;
+-------------+--------------------------+
|id           |1                         |
+-------------+--------------------------+
|select_type  |SIMPLE                    |
+-------------+--------------------------+
|table        |film                      |
+-------------+--------------------------+
|partitions   |NULL                      |
+-------------+--------------------------+
|type         |ALL                       |
+-------------+--------------------------+
|possible_keys|PRIMARY,idx_fk_language_id|
+-------------+--------------------------+
|key          |NULL                      |
+-------------+--------------------------+
|key_len      |NULL                      |
+-------------+--------------------------+
|ref          |NULL                      |
+-------------+--------------------------+
|rows         |1000                      |
+-------------+--------------------------+
|filtered     |100                       |
+-------------+--------------------------+
|Extra        |Using where               |
+-------------+--------------------------+

索引合并策略有时候效果非常不错,但更多的时候,它说明了表中的索引建得很糟糕

  • 当优化器需要对多个索引做相交操作时(通常有多个 AND 条件),通常意味着需要一个包含所有相关列的多列索引。

  • 当优化器需要对多个索引做联合操作时(通常有多个 OR 条件),通常需要在算法的缓存、排序和合并操作上耗费大量 CPU 和内存资源,尤其是当其中有些索引的选择性不高,需要合并扫描返回的大量数据的时候。

    更重要的是,优化器不会把这些操作计算到“查询成本”(cost)中,优化器只关心随机页面读取。这会使得查询的成本被“低估”,导致该执行计划还不如直接进行全表扫描。这样做不但会消耗更多的 CPU 和内存资源,还可能会影响并发的查询,但如果单独运行这样的查询则往往会忽略对并发性的影响。通常来说,使用 UNION 改写查询,往往是最好的办法。

如果在 EXPLAIN 中看到有索引合并,那么就应该好好检查一下查询语句的写法和表的结构,看是不是已经是最优的。也可以通过参数 optimizer_switch 来关闭索引合并功能,还可以使用 IGNORE INDEX 语法让优化器强制忽略掉某些索引,从而避免优化器使用包含索引合并的执行计划。

最后更新于