玖叶教程网

前端编程开发入门

详解MySQL数据库中的联合索引与最左匹配原则

概述

之前在网上看到过很多关于mysql联合索引最左前缀匹配的文章,讲的都比较泛,所以今天在这里自己整理一下这方面的内容,做个备忘!


Mysql中联合索引的最左匹配原则

在Mysql建立多列索引(联合索引)有最左前缀的原则,即最左优先。

如果我们建立了一个2列的联合索引(col1,col2),实际上已经建立了两个联合索引(col1)、(col1,col2);

如果有一个3列索引(col1,col2,col3),实际上已经建立了三个联合索引(col1)、(col1,col2)、(col1,col2,col3)。

说明:

1、b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+树是按照从左到右的顺序来建立搜索树的,比如当(小明,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道第一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。

2、比如当(小明,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于小明的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。

这里要注意联合索引的顺序,如果联合索引是(col1,col2,col3),但是where col3=xx and col2=xx 这种实际上是不会用到索引的。


mysql创建联合索引的意义

一个顶三个

建了一个(a,b,c)的复合索引,那么实际等于建了(a),(a,b),(a,b,c)三个索引,因为每多一个索引,都会增加写操作的开销和磁盘空间的开销。对于大量数据的表,这可是不小的开销。

覆盖索引

同样的有复合索引(a,b,c),如果有如下的sql: select a,b,c from table where a=1 and b = 1。那么MySQL可以直接通过遍历索引取得数据,而无需回表,这减少了很多的随机io操作。减少io操作,特别的随机io其实是dba主要的优化策略。所以,在真正的实际应用中,覆盖索引是主要的提升性能的优化手段之一

索引列越多,通过索引筛选出的数据越少

有1000W条数据的表,有如下sql:

select * from table where a = 1 and b =2 and c = 3

假设假设每个条件可以筛选出10%的数据,如果只有单值索引,那么通过该索引能筛选出1000W*10%=100w 条数据,然后再回表从100w条数据中找到符合b=2 and c= 3的数据,然后再排序,再分页;如果是复合索引,通过索引筛选出1000w*10%*10%*10%=1w,然后再排序、分页,哪个更高效就不用说了吧

创建联合索引时列的选择原则

  1. 经常用的列优先(最左匹配原则)
  2. 离散度高的列优先(离散度高原则)
  3. 宽度小的列优先(最少空间原则)

列的离散性计算:count(distinct col)/ count(col)

例如:

id列一共9列都不重复 9/9 = 1

性别列一共9列只有(男或者女)两列 2/9 约等于0.2

离散性越高选择性越大


实例演示

1、创建测试表

CREATE TABLE `student` (
 `id` int(11) NOT NULL,
 `name` varchar(10) NOT NULL,
 `age` int(11) NOT NULL,
 PRIMARY KEY (`id`),
 KEY `idx_id_name_age` (`id`,`name`,`age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

2、填充100w测试数据

DROP PROCEDURE pro10;
CREATE PROCEDURE pro10()
BEGIN
 DECLARE i INT;
 DECLARE char_str varchar(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
 DECLARE return_str varchar(255) DEFAULT '';
 DECLARE age INT;
 SET i = 1;
 WHILE i < 5000000 do
 SET return_str = substring(char_str, FLOOR(1 + RAND()*62), 8);
 SET i = i+1;
 SET age = FLOOR(RAND() * 100);
 INSERT INTO student(id, name, age) values(i, return_str, age);
 END WHILE;
END;
 
CALL pro10();

3、场景测试

EXPLAIN SELECT * FROM student WHERE id = 2;

这里可以看到该查询使用到了索引

EXPLAIN SELECT * FROM student WHERE id = 2 AND name = 'WXYZ0123';

可以看到该查询使用到了索引

EXPLAIN SELECT * FROM student WHERE id = 2 AND name = 'WXYZ0123' and age = 51;

可以看到该查询使用到了索引

EXPLAIN SELECT * FROM student WHERE id = 2 AND age = 51;

可以看到该查询使用到了索引

EXPLAIN SELECT * FROM student WHERE name = 'WXYZ0123' AND age = 51;

可以看到该查询没有使用到索引,类型为index,查询行数为812567,几乎进行了全表扫描,由于组合索引只针对最左边的列进行了排序,对于name、age只能进行全部扫描

EXPLAIN SELECT * FROM student WHERE name = 'WXYZ0123' AND id = 2;
EXPLAIN SELECT * FROM student WHERE age = 51 AND id = 2;
EXPLAIN SELECT * FROM student WHERE name = 'WXYZ0123' and age = 51 AND id = 2;

可以看到如上查询也使用到了索引,id放前面和放后面查询到的结果是一样的,MySQL会找出执行效率最高的一种查询方式,就是先根据id进行查询。


总结

如上测试,可以看到只要查询条件的列中包含组合索引最左边的那一列,不管该列在查询条件中的位置,都会使用索引进行查询。

后面会分享更多mysql方面内容,感兴趣的朋友可以关注下!!

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言