MySQL优化:单索引的选择-index_merge

1.多个单索引

where子句后有多个查询条件,MySQL 5.1以前即使分别为每个条件建立索引,查询优化器也只会选择其中一个区分度高索引。笔者使用5.7.20,以下分析均基于此版本,不同版本的MySQL查询优化器给出的结果存在差异。下面开始分情况讨论MySQL查询优化器给出的索引结果。

  • where子句有两个个and条件分别且三个字段都有索引
mysql> EXPLAIN SELECT * FROM order t WHERE t.PrdModel = 'NX549J' AND t.Destination = '国内' AND t.OEM = '福盛创新'; 
+----+-------------+-------+------------+-------------+--------------------------+--------------+---------+------+------+----------+--------------------------------------------+
| id | select_type | table | partitions | type        | possible_keys            | key          | key_len | ref  | rows | filtered | Extra                                      |
+----+-------------+-------+------------+-------------+--------------------------+--------------+---------+------+------+----------+--------------------------------------------+
|  1 | SIMPLE      | t     | NULL       | index_merge | PrdModel,Destination,OEM | OEM,PrdModel | 153,153 | NULL |  614 |    50.00 | Using intersect(OEM,PrdModel); Using where |
+----+-------------+-------+------------+-------------+--------------------------+--------------+---------+------+------+----------+--------------------------------------------+
1 row in set, 1 warning (0.00 sec)


mysql> EXPLAIN SELECT * FROM order t WHERE t.PrdModel = 'NX549J' AND t.Destination = '国内' AND t.OEM = 'XXX技术有限公司'; 
+----+-------------+-------+------------+-------------+--------------------------+----------------------+---------+------+--------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type        | possible_keys            | key                  | key_len | ref  | rows   | filtered | Extra                                              |
+----+-------------+-------+------------+-------------+--------------------------+----------------------+---------+------+--------+----------+----------------------------------------------------+
|  1 | SIMPLE      | t     | NULL       | index_merge | PrdModel,Destination,OEM | PrdModel,Destination | 153,153 | NULL | 320463 |    50.00 | Using intersect(PrdModel,Destination); Using where |
+----+-------------+-------+------------+-------------+--------------------------+----------------------+---------+------+--------+----------+----------------------------------------------------+
1 row in set, 1 warning (0.00 sec)

上例告诉我们实际的索引应用的index_merge,从三索引中选其二,奇怪的是每次选择结果并不是固定,第一次选择intersect(OEM,PrdModel),第二次选择intersect(PrdModel,Destination),尝试从区分度的角度查找原因,上例中三个索引的区分度分别为PrdModel(35045),Destination(22982),OEM(6195)。发现并不是简单的从区分度最高的索引选其二。那么索引合并选择的依据是什么呢?下面尝试从合并的复杂度方面比较:

索引/取值(记录数)/参数 PrdModel Destination OEM
intersect(PrdModel,Destination) NX549J(320147) 国内(8077050) XXX技术有限公司(5418791)
intersect(OEM,PrdModel) NX549J(320147) 国内(8077050) 福盛创新(11094)
intersect(PrdModel,Destination) NX549J(320147) 国内(8077050) is not null (21800063)
intersect(PrdModel,OEM) NX549J(320147) 国内(8077050) is null (537463)
  • 下面借助 官网文档 尝试理解index_merge的使用及选择机制。
    index_merge用在范围查询中,然后尝试合并多个索引检索的结果。笔者分别尝试两个、三个、四个条件的索引发现,index_merge只会选择其二合并索引,这一点官方没有特别说明。官方提到,index_merge只能扫描单表,不能跨表扫描。index merge: 同一个表的多个索引的范围扫描可以对结果进行合并,合并方式分为三种:union, intersection, 以及它们的组合(先内部intersect然后在外面union)。
    官方给出了四个例子。

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);

经过测试,发现前三种能够得到index_merge,第四种情况看起来有点怪,两个连表条件,一个和一个查询条件,并不能使用index_merge。

  • 注意,下面是官方给出的建议,where后的条件写的比较复杂时,可能引起查询优化器的错误选择索引,应遵循以下原则:

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

  • index merge 之 intersect
    index intersect merge就是多个索引条件扫描得到的结果进行交集运算,结果如下:

key_part1=const1 AND key_part2=const2 ... AND key_partN=constN

  • index merge 之 union
    index uion merge就是多个索引条件扫描,对得到的结果进行并集运算,显然是多个条件之间进行的是 OR 运算。

key1 = 1 OR key2 = 2 OR key3 = 3;

  • index merge 之 Sort-Union
    简言之,就是需要先取交集再取并集,如多个OR的范围查询,可能有重复的记录,需要先取并集再取交集。

SELECT * FROM tbl_name WHERE key_col1 < 10 OR key_col2 < 20;

你可能感兴趣的:(MySQL优化:单索引的选择-index_merge)