MySQL索引合并Index Merge 一条SQL使用多个索引情况分析,以及索引合并失效情况

索引合并介绍

MySQL在5.0版本加入了索引合并优化(Index Merge),索引合并可以同时查询多个索引的范围扫描,并将其结果合并为一个。索引合并只可以合并来自单个表的索引扫描,而不支持跨多个表的扫描。合并可以生成其基础扫描的并集(unions)、交集(intersections)或交集的并集(unions-of-intersections)。

简单点说,索引合并,可以让一条SQL使用多个索引。然后对这些索引取交集、并集、交集的并集,从而减少读表次数,提高查询效率。

怎么确定使用了索引合并

如果SQL使用了索引合并,在执行计划explain输出中,type列会显示index_merge,key列会显示所有使用的索引,extra列会显示:

  • Using intersect(...)
  • Using union(...)
  • Using sort_union(...)

intersect、union、sort_union分别对于的是索引合并算法:

  • Index Merge Intersection Access Algorithm
  • Index Merge Union Access Algorithm
  • Index Merge Sort-Union Access Algorithm

可以使用索引合并的查询示例

SELECT * FROM tbl_name WHERE key1 = 10 OR key2 = 20;

SELECT * FROM tbl_name
WHERE (key1 = 10 OR key2 = 20) AND non_key = 30;

SELECT * FROM t1, t2
WHERE (t1.key1 IN (1,2) OR t1.key2 LIKE 'value%')
AND t2.key1 = t1.some_col;

SELECT * FROM t1, t2
WHERE t1.key1 = 1
AND (t2.key1 = t1.some_col OR t2.key2 = t1.some_col2);

索引合并具有以下限制

1.带有深度AND/OR嵌套的复杂WHERE子句,并且MySQL没有选择最佳计划时索引合并会失效,可以使用以下转换:

(x AND y) OR z => (x OR z) AND (y OR z)
(x OR y) AND z => (x AND z) OR (y AND z)

2.索引合并不适用于全文索引。

交集索引合并算法(Index Merge Intersection Access Algorithm)

交集索引合并算法的使用原则

1.二级索引的等值查询,如果是联合索引查询条件必须包含所有联合索引列。
2.聚簇索引的范围查询。

索引合并实战分析

CREATE TABLE `t_demo2` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `a` int(15) DEFAULT NULL,
  `b` varchar(15) DEFAULT NULL,
  `c` varchar(15) DEFAULT NULL,
  `d` bigint(15) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

二级索引(单列索引)测试

删除其他索引,重新新建两个单列的二级索引

ALTER TABLE `t_demo` ADD INDEX `idx_a`(a) USING BTREE;
ALTER TABLE `t_demo` ADD INDEX `idx_b`(b) USING BTREE;

生效情况

mysql> EXPLAIN SELECT * FROM `t_demo` WHERE a = 5132 AND b = 'ss';
+----+-------------+--------+------------+-------------+---------------+-------------+---------+------+------+----------+-------------------------------------------+
| id | select_type | table  | partitions | type        | possible_keys | key         | key_len | ref  | rows | filtered | Extra                                     |
+----+-------------+--------+------------+-------------+---------------+-------------+---------+------+------+----------+-------------------------------------------+
|  1 | SIMPLE      | t_demo | NULL       | index_merge | idx_a,idx_b   | idx_a,idx_b | 5,48    | NULL |    1 |   100.00 | Using intersect(idx_a,idx_b); Using where |
+----+-------------+--------+------------+-------------+---------------+-------------+---------+------+------+----------+-------------------------------------------+

mysql> EXPLAIN SELECT * FROM `t_demo` WHERE a = 5132 AND b = 'ss' AND c = 'bYJYf';
+----+-------------+--------+------------+-------------+---------------+-------------+---------+------+------+----------+-------------------------------------------+
| id | select_type | table  | partitions | type        | possible_keys | key         | key_len | ref  | rows | filtered | Extra                                     |
+----+-------------+--------+------------+-------------+---------------+-------------+---------+------+------+----------+-------------------------------------------+
|  1 | SIMPLE      | t_demo | NULL       | index_merge | idx_a,idx_b   | idx_a,idx_b | 5,48    | NULL |    1 |    10.00 | Using intersect(idx_a,idx_b); Using where |
+----+-------------+--------+------------+-------------+---------------+-------------+---------+------+------+----------+-------------------------------------------+

失效情况

mysql> EXPLAIN SELECT * FROM `t_demo` WHERE a IN (5132, 5133) AND b = 'ss';
+----+-------------+--------+------------+-------+---------------+-------+---------+------+------+----------+------------------------------------+
| id | select_type | table  | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra                              |
+----+-------------+--------+------------+-------+---------------+-------+---------+------+------+----------+------------------------------------+
|  1 | SIMPLE      | t_demo | NULL       | range | idx_a,idx_b   | idx_a | 5       | NULL |   49 |     0.15 | Using index condition; Using where |
+----+-------------+--------+------------+-------+---------------+-------+---------+------+------+----------+------------------------------------+

mysql> EXPLAIN SELECT * FROM `t_demo` WHERE a <= 5132 AND b = 'ss';
+----+-------------+--------+------------+------+---------------+-------+---------+-------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key   | key_len | ref   | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+-------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | t_demo | NULL       | ref  | idx_a,idx_b   | idx_b | 48      | const |  288 |    50.00 | Using where |
+----+-------------+--------+------------+------+---------------+-------+---------+-------+------+----------+-------------+

失效原因:
1.使用了范围查询 IN
2.使用了范围查询 <=

联合索引测试

删除其他索引,重新新建两个联合索引

ALTER TABLE `t_demo` ADD INDEX `idx_ab`(a,b) USING BTREE;
ALTER TABLE `t_demo` ADD INDEX `idx_cd`(c,d) USING BTREE;

生效情况

mysql> EXPLAIN SELECT * FROM `t_demo` WHERE a = 5132 AND b = 'ss' AND c = 'bYJYf' AND d = 1621331318479;
+----+-------------+--------+------------+-------------+---------------+---------------+---------+------+------+----------+----------------------------------------------------------+
| id | select_type | table  | partitions | type        | possible_keys | key           | key_len | ref  | rows | filtered | Extra                                                    |
+----+-------------+--------+------------+-------------+---------------+---------------+---------+------+------+----------+----------------------------------------------------------+
|  1 | SIMPLE      | t_demo | NULL       | index_merge | idx_ab,idx_cd | idx_cd,idx_ab | 57,53   | NULL |    1 |   100.00 | Using intersect(idx_cd,idx_ab); Using where; Using index |
+----+-------------+--------+------------+-------------+---------------+---------------+---------+------+------+----------+----------------------------------------------------------+

失效情况

mysql> EXPLAIN SELECT * FROM `t_demo` WHERE a = 5132 AND c = 'bYJYf';
+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key    | key_len | ref   | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | t_demo | NULL       | ref  | idx_ab,idx_cd | idx_cd | 48      | const |    5 |     1.00 | Using where |
+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------+

mysql> EXPLAIN SELECT * FROM `t_demo` WHERE a = 5132 AND b = 'ss' AND c = 'bYJYf';
+----+-------------+--------+------------+------+---------------+--------+---------+-------------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key    | key_len | ref         | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+--------+---------+-------------+------+----------+-------------+
|  1 | SIMPLE      | t_demo | NULL       | ref  | idx_ab,idx_cd | idx_ab | 53      | const,const |    5 |     1.00 | Using where |
+----+-------------+--------+------------+------+---------------+--------+---------+-------------+------+----------+-------------+

mysql> EXPLAIN SELECT * FROM `t_demo` WHERE a <= 5132 AND b = 'ss' AND c = 'bYJYf' AND d = 1621331318479;
+----+-------------+--------+------------+------+---------------+--------+---------+-------------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key    | key_len | ref         | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+--------+---------+-------------+------+----------+-------------+
|  1 | SIMPLE      | t_demo | NULL       | ref  | idx_ab,idx_cd | idx_cd | 57      | const,const |    2 |     5.00 | Using where |
+----+-------------+--------+------------+------+---------------+--------+---------+-------------+------+----------+-------------+

失效原因:
1.查询条件未覆盖所有联合索引列
2.查询条件未覆盖第二个联合索引列
3.使用了范围查询 <=

聚簇索引

删除其他索引,重新新建一个单列的二级索引

ALTER TABLE `t_demo` ADD INDEX `idx_a`(a) USING BTREE;

生效情况

mysql> EXPLAIN SELECT * FROM `t_demo` WHERE id <= 10000 AND a = 5132;
+----+-------------+--------+------------+-------------+---------------+---------------+---------+------+------+----------+---------------------------------------------+
| id | select_type | table  | partitions | type        | possible_keys | key           | key_len | ref  | rows | filtered | Extra                                       |
+----+-------------+--------+------------+-------------+---------------+---------------+---------+------+------+----------+---------------------------------------------+
|  1 | SIMPLE      | t_demo | NULL       | index_merge | PRIMARY,idx_a | idx_a,PRIMARY | 13,8    | NULL |    1 |   100.00 | Using intersect(idx_a,PRIMARY); Using where |
+----+-------------+--------+------------+-------------+---------------+---------------+---------+------+------+----------+---------------------------------------------+

失效情况

mysql> EXPLAIN SELECT * FROM `t_demo` WHERE id = 87760 AND a = 5132;
+----+-------------+--------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table  | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+--------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t_demo | NULL       | const | PRIMARY,idx_a | PRIMARY | 8       | const |    1 |   100.00 | NULL  |
+----+-------------+--------+------------+-------+---------------+---------+---------+-------+------+----------+-------+

失效原因:
1.聚簇索引的等值查询直接使用聚簇索引效率会更高,不需要使用索引合并。

并集索引合并算法(Index Merge Union Access Algorithm)

执行流程和交集算法类似,先根据不同索引查询出有序的主键集合,然后求并集。

例如:WHERE a = 5132 OR b = 'ss'

mysql> EXPLAIN SELECT * FROM `t_demo` WHERE a = 5132 OR b = 'ss';
+----+-------------+--------+------------+-------------+---------------+-------------+---------+------+------+----------+---------------------------------------+
| id | select_type | table  | partitions | type        | possible_keys | key         | key_len | ref  | rows | filtered | Extra                                 |
+----+-------------+--------+------------+-------------+---------------+-------------+---------+------+------+----------+---------------------------------------+
|  1 | SIMPLE      | t_demo | NULL       | index_merge | idx_a,idx_b   | idx_a,idx_b | 5,48    | NULL |  320 |   100.00 | Using union(idx_a,idx_b); Using where |
+----+-------------+--------+------------+-------------+---------------+-------------+---------+------+------+----------+---------------------------------------+

需要注意的是,OR连接的条件必须都有索引。如果c字段没有索引,那么加上 OR c = 'bYJYf1' 后,不会走索引合并

mysql> EXPLAIN SELECT * FROM `t_demo` WHERE a = 5132 OR b = 'ss' OR c = 'bYJYf1';
+----+-------------+--------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | t_demo | NULL       | ALL  | idx_a,idx_b   | NULL | NULL    | NULL | 187882 |    19.01 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+--------+----------+-------------+

排序并集索引合并算法(Index Merge Sort-Union Access Algorithm)

执行流程和并集算法类似,但是是范围查询,联合索引也可以不覆盖所有的索引列。

排序并集算法和并集算法的区别在于,排序并集算法必须首先获取所有行的行主键,并在返回任何行之前对其进行排序。

例如:WHERE a < 200 OR b < 'aa'

mysql> EXPLAIN SELECT * FROM `t_demo` WHERE a < 200 OR b < 'aa';
+----+-------------+--------+------------+-------------+---------------+-------------+---------+------+------+----------+--------------------------------------------+
| id | select_type | table  | partitions | type        | possible_keys | key         | key_len | ref  | rows | filtered | Extra                                      |
+----+-------------+--------+------------+-------------+---------------+-------------+---------+------+------+----------+--------------------------------------------+
|  1 | SIMPLE      | t_demo | NULL       | index_merge | idx_a,idx_b   | idx_a,idx_b | 5,48    | NULL | 4114 |   100.00 | Using sort_union(idx_a,idx_b); Using where |
+----+-------------+--------+------------+-------------+---------------+-------------+---------+------+------+----------+--------------------------------------------+

需要注意的是,并不是所有范围查询都能命中排序并集算法,当查询范围扩大,索引合并会失效。

例如,将上面例子修改一下:a < 2000 OR b < 'aa',将不会走索引合并

mysql> EXPLAIN SELECT * FROM `t_demo` WHERE a <= 2000 OR b <= 'aa';
+----+-------------+--------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | t_demo | NULL       | ALL  | idx_a,idx_b   | NULL | NULL    | NULL | 187882 |    55.55 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+--------+----------+-------------+

猜你喜欢

转载自blog.csdn.net/Anenan/article/details/130320519