四、联合索引最左前缀原理

在这里插入图片描述

B+Tree联合索引底层数据结构

单列索引:是指由一个字段(单个列)组成的索引;
联合索引:联合索引又叫复合索引,是指由多个字段(字段是有顺序的)组成的索引,比如:
CREATE TABLE users (
id int(11) NOT NULL AUTO_INCREMENT,
nick varchar(30) DEFAULT NULL,
phone varchar(25) NOT NULL,
password varchar(64) DEFAULT NULL,
email varchar(50) DEFAULT NULL,
account varchar(15) DEFAULT NULL,
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_nick_phone (nick,phone,create_time) USING BTREE --联合索引
) ENGINE=InnoDB AUTO_INCREMENT=9980018 DEFAULT CHARSET=utf8mb4;

单列索引是特殊的联合索引,是联合索引字段列个数为1的特例;
联合索引叶子节点没有存储数据,节省磁盘空间,其数据结构如下:

在这里插入图片描述


索引键index(col1, col2, col3)如何排序?
先根据第一个字段col1排序,然后根据第二个字段col2排序,如果前两个一样,则根据第三个字段col3排序;
B+Tree索引适用于全键值匹配查询,键值范围匹配查询,键值前缀匹配查询;
其中键值前缀查询只适用于根据键值的最左前缀查询

联合索引的最左前缀原理

CREATE TABLE users (
id int(11) NOT NULL AUTO_INCREMENT,
nick varchar(30) DEFAULT NULL,
phone varchar(25) NOT NULL,
password varchar(64) DEFAULT NULL,
email varchar(50) DEFAULT NULL,
account varchar(15) DEFAULT NULL,
create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_nick_phone (nick,phone,create_time) USING BTREE --联合索引
) ENGINE=InnoDB AUTO_INCREMENT=9980018 DEFAULT CHARSET=utf8mb4;

1、全值匹配(全列匹配、全键值匹配)

联合索引中的每一个索引字段都是where查询的条件;
select * from users where nick =‘cat’ and phone = ‘13700000000’;
select * from users where phone = ‘13700000000’ and nick =‘cat’;
select * from users where phone = ‘13700000000’ and nick in(‘cat’);
select * from users where phone in (‘13700000000’) and nick in (‘cat’);
select * from users where nick = ‘cat’ or phone = ‘13700000000’;
select * from users where phone in (‘13700000000’) or nick in (‘cat’);

当按照索引中所有列进行精确匹配(这里精确匹配指“=”或“IN”匹配,每一列都能精确匹配)时,索引可以被用到;
注意,理论上索引对顺序是敏感的,但是由于MySQL的查询优化器会自动调整where子句的条件顺序以使用适合的索引,例如我们将where中的条件顺序颠倒,也用到了索引,效果一样;

2、最左前缀匹配
select * from users where nick = ‘cat’;
select * from users where nick = ‘cat’ and create_time= ‘2020-02-09’
select * from users where phone = ‘13700000000’

这种情况指的是只使用索引的第一列进行匹配,如果使用索引的第二列查询,无法命中索引;
如果where条件没有使用到最左列,无法使用索引,根据phone 、create_time查询无法使用索引,因为这两列不是最左列。

3、查询条件使用索引列的精确匹配,中间某个条件未提供
select * from users where nick = ‘cat’ and create_time= ‘2020-02-09’
用到索引了

4、查询条件中没有索引第一列
select * from users where phone = ‘13700000000’ create_time= ‘2020-02-09’
由于不是最左前缀,索引这样的查询显然不能使用索引,where条件中没最左列;

5、匹配列前缀字符串
这种情况指的是只匹配一个列值的开头部分,比如nick=’cat1129’,采用like过滤匹配 cat11%;
select * from users where nick like ‘cat11%’; --命中索引
注:这里匹配越精确,效率也越高,如果匹配 cat%,mysql查询优化器可能会进行全表扫描,比如所有的nick都是cat开头的,所以要尽可能的精确like的前缀;
如果where条件没有使用到最左列,无法使用索引,根据phone、create_time查询无法使用到索引,这两列不是最左列,也无法查询 nick like %at001 以结尾匹配查询也不行;
如果查询中有某个列的范围查询,则右边所有列都无法使用索引查找,比如
where nick = ‘cat’and phone like ‘13720010%’ and create_time = ‘2020-02-09’,该查询只能使用索引的前两列,优化的话,如果like的结果较少,可以用in;
假设phone 只有两个:13720000000 13720000001,或者只有10几个;
where nick = ‘cat’and phone in (13720000000, 13720000001) and create_time = ‘2020-02-09’;(全键值匹配的查询,更精准的定位,读取的数据更少,效率更好)

6、匹配范围值
select * from users where nick in (‘cat’, ‘cat10’); --走索引
select * from users where phone in (‘13720010000’, ‘13700040000’); --不走索引

匹配索引键的第一列范围值(必须是最左前缀),只能命中第一列的范围,范围列后面的列无法用到索引;
范围包括 >、<、!=、in、is null、between … and 等;

7、精确匹配某一列并范围匹配另一列
select * from users where nick = ‘cat’ and phone in (‘13720010000’, ‘13700040000’);
第一列精确匹配,第二列范围匹配;

8、查询条件中有函数或表达式
如果查询条件中有函数或表达式,则MySQL不会为该列使用索引;
explain select * from users where id + 1 = 2;
explain select * from users where left(nick, 5) = ‘cat99’;

9、只访问索引列的查询
只查询有索引的字段,这种查询叫“覆盖索引”查询,在索引键中就找到了数据,不需要获取数据行,也就是不需要回表中获取数据,性能得到提高,所以我们对于值需要查询一两个字段的业务,就不要采用select * from;
select nick, phone, create_time from users where phone = ‘1372001000’;走索引,覆盖索引
select * from users where phone = ‘1372001000’; 不走索引

10、排序分组查询
B+Tree索引是有序的数据结构,所以除了按索引键查询外,还可以按索引键进行order by排序;

11、分页limit查询
SELECT a.* FROM users where nick like ‘cat52%’ LIMIT 3000000, 15;
优化如下:覆盖索引查询 id
SELECT a.* FROM users a, (select id from users where nick like ‘cat52%’ LIMIT 3000000,15) b where a.id = b.id

索引离散性

既然索引可以加快查询速度,那么是不是只要是查询语句条件都建上索引?
答案是否定的,索引虽然加快了查询速度,但索引也有代价:
索引文件本身要消耗存储空间,同时索引会加重插入、删除和修改记录时的负担,MySQL在运行时也要消耗资源维护索引,因此索引并不是越多越好;
表记录比较少,例如一两千条甚至只有几百条记录的表,没必要建索引,让查询做全表扫描即可,一般记录在2000条以上可以酌情考虑建立索引;
列的离散性

列的离散性
公式:count(distinct col)/ count(col)
比值越大离散性越好,离散性越好则选择性越好,

在这里插入图片描述


索引的离散性较低不建议建索引,离散性较低也就是选择性较低(B+Tree要走很多个分叉去找数据),所谓索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值:
Index Selectivity = Cardinality / #T
选择性的取值范围为(0, 1],选择性越高的索引价值越大,这是由B+Tree的性质决定的;
。 如果列的离散性很差,一般创建索引的价值也不大;
可以采用联合索引提高列的离散性;
SELECT count(DISTINCT(concat(nick, phone)))/count() AS selectivity FROM users;
SELECT count(DISTINCT(concat(nick, right(phone,4))))/count(
) AS selectivity FROM users;

我们把这个前缀索引建上:
ALTER TABLE employees.employees ADD INDEX idx_nick_phone (nick, phone(4));
使用前缀索引兼顾了索引大小和查询速度,但其缺点是不能用于ORDER BY和GROUP BY操作,也不能用于覆盖索引Covering index(即当索引本身包含查询所需全部数据时,不再回表访问数据文件本身);

覆盖索引

如果查询列可通过索引节点中的关键字直接返回,则该索引称之为覆盖索引;
覆盖索引可减少数据库IO,可提高查询性能;
创建联合索引原则
1,经常用的列优先 【最左匹配原则】
2,选择性(离散性)高的列优先【离散性高原则】
3,宽度小的列优先【最少空间原则】 varchr 3 nchar(15)
explain select * from users where nick = ‘cat’;
explain select * from users where nick = ‘cat’ and phone = ‘13700000000’;

InnoDB的主键选择

在使用InnoDB存储引擎时,请永远使用一个与业务无关的自增字段作为主键;
经常也有人采用像手机号、身份证号、uuid这种唯一字段作为主键;
从数据库索引优化角度看,使用InnoDB引擎而不使用自增主键绝对不是一个好主意;
InnoDB使用聚集索引,数据记录本身被存于主键索引(一颗B+Tree)的叶子节点上,这就要求同一个叶子节点内的各条数据记录按主键顺序存放,当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点位置,如果使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页;

在这里插入图片描述


这样就会形成一个紧凑的索引结构,近似顺序填满,每次插入也不需要移动已有数据,效率较高,索引维护开销小;
如果使用非自增主键(如手机号、身份证号等),每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置:

在这里插入图片描述


此时MySQL不得不为了将新记录插到合适位置而移动数据,增加了开销,同时频繁的移动、分页操作造成了大量的碎片,得到了不紧凑的索引结构;
所以尽量在InnoDB上采用自增字段做主键;

索引优化小结

正确的创建合适的索引是提升数据库查询性能的基础
索引能极大的减少存储引擎需要扫描的数据量;
索引可以把随机IO变成顺序IO;
索引可以帮助我们在进行排序、分组等操作时,避免使用临时表;
索引列的数据长度能少则少(长度太长占空间,会让树高度变高);
索引一定不是越多越好,一定是创建合适的索引,一张表一般不超过6个;
匹配列前缀可用到索引,像like 9999%可以用到索引,而like %9999%、like %9999用不到索引;
匹配范围值可用用索引,order by 也可用到索引;
多用指定列查询,只返回自己想到的数据列,少用select *,因为有可能命中覆盖索引;
联合索引中如果不是按照索引最左列开始查找,无法使用索引;
联合索引中精确匹配最左前列并范围匹配另外一列可以用到索引;
联合索引中如果查询中有某个列的范围查询,则其右边的所有列都无法使用索引;
索引在数据量比较小的情况下,看不出明显的效果,当数据量较大时,恰当的索引将极大提升查询性能;

其实数据库索引调优是一项技术活,因为实际情况千变万化,而且MySQL本身存在很复杂的机制,如查询优化策略和各种引擎的实现差异等都会使情况变得更加复杂,但是理解并掌握以上索引底层数据结构的理论基础(B+Tree),才能对调优策略进行合理推断并了解其背后的原理,然后结合实践不断实验和摸索,从而真正达到高效使用MySQL索引的目的;

相关文章

学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习...
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面...
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生...
Can’t connect to local MySQL server through socket \'/v...
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 ...
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服...