定义
不同于对某列建立索引,可以同时对多个列建立索引,也称复合索引、联合索引。
测试表
CREATE TABLE `test` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
`c1` int(10) NOT NULL COMMENT 'c1',
`c2` int(10) NOT NULL COMMENT 'c2',
`c3` int(10) NOT NULL COMMENT 'c3',
`c4` int(10) NOT NULL COMMENT 'c4',
PRIMARY KEY (`id`) USING BTREE,
KEY `IDX_C1C2C3` (`c1`,`c2`,`c3`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
其中对c1、c2、c3建立组合索引。
存储结构
单索引的存储结构很好理解,非叶子节点中存储单索引的值。当有多个列组成组合索引时,存储结构如图,摘自《高性能Mysql,第三版》
组合索引对多个值进行排序是依照定义时索引的的顺序。这里按照c1、c2、c3的顺序定义,即在存储时先按照c1排序,c1相同时按c2进行排序,c2相同时按c3排序。所以查询时想使索引中的c2列生效,那么条件中也要有c1。想使c3列生效,c1和c2必须也要有。这也就是常说的组合索引最左前缀匹配。
测试
- 全匹配
EXPLAIN SELECT * FROM test WHERE c1 = 111 and c2 = 222 and c3 = 333
key_len为12,正好为三个字段的长度,证明三个字段的索引全部生效了。
where后的查询顺序不影响组合索引的使用
EXPLAIN SELECT * FROM test WHERE c2 = 222 and c1 = 111 and c3 = 333
只要包含了需要的索引,explain的结果和上面的一样,不再贴出。
- 不含c1
EXPLAIN SELECT * FROM test WHERE c2 = 222 and c3 = 333;
搜索条件不含c1,发现走的全表扫描,组合索引没有生效。
- 不含c2
EXPLAIN SELECT * FROM test WHERE c1 = 111 and c3 = 333
虽然用到了联合索引,但是实际使用长度只有4,也就是只用了c1。
- 不含c3
EXPLAIN SELECT * FROM test WHERE c1 = 111 and c2 = 222
使用了联合索引,实际使用长度为8,c1和c2都用上了。
- 匹配列
这里把c2改成varchar(10)
EXPLAIN SELECT * FROM test WHERE c1 = 111 and c2 LIKE '22' and c3 = 333
key_len为40,c1、c2、c3全部使用了。
EXPLAIN SELECT * FROM test WHERE c1 = 111 and c2 LIKE '%22' and c3 = 333
key_len为4,只用到了c1。结合组合索引的存储也可以理解,匹配前缀的时候,可以用到排序,但是匹配后缀,排序就失效了,所以从c2这里就断掉了。
- 匹配范围值
EXPLAIN SELECT * FROM test WHERE c1 = 111 and c2 < 22 and c3 = 33
key_len为8,c1和c2生效,范围搜索后的c3失效了。
- 其他失效情况
EXPLAIN SELECT * FROM test WHERE c1 = 111 and c2 + 2 = 22 and c3 = 33
计算式会使该列索引失效,这里只有c1生效了。
EXPLAIN SELECT * FROM test WHERE c1 = 111 and c2 != 22 and c3 = 33
!=使得整个组合索引失效。
再次将c2改为varchar(10)
EXPLAIN SELECT * FROM test WHERE c1 = 111 and c2 not like '22' and c3 = 33
只有c1生效了。
EXPLAIN SELECT * FROM test WHERE c1 = 111 and UPPER(c2) = '222' and c3 = 33
只有c1生效了。
参考
- 《高性能mysql,第三版》
- http://blog.codinglabs.org/articles/theory-of-mysql-index.html