MySQL中B+树索引,聚簇索引,二级索引,辅助索引,回表,索引生效条件

对于MySQL,我们经常说调优有一个手段就是加索引,那么为什么加索引能够优化查询,是不是加了索引查询就快了 ?
在MySQL中,存储的单元并不是按照我们理解的一条一条记录,而是按照页来进行存储的,MySQL中默认一个页page的大小是连续的16KB,这个页中会记录多条数据。
为什么会设置到16KB,这是因为一般系统中,磁盘读取文件每次不是需要多少就读取多少,而是会预读一部分,而预读的大小就是一个page(一般系统中指的是操作系统不是mysql,一个page在4KB或者8KB左右),而mysql中的page是连续的16KB,可以顺序磁盘读取出来

如果我们在写入的时候,不对这些数据进行一些处理,让它的存储方便我们查找,那么我们要查询表中的一条数据的时候,肯定是需要进行全表扫描的。

这时候,为了能够快速查询,就需要我们对写入的数据进行一定的整理,按照一定的结构和算法,方便我们能够快速查询,数据库一般都提供了索引的概念。

MySQL中InnoDB引擎提供了B+树索引,为什么使用B+树呢,而不是普通的二叉树呢?
普通二叉树一般查询时间为O(log2n),插入的时候以第一个插入的节点为根节点不会调整,可能会导致整棵树不均衡,极端情况下,树可能只有左子树或右子树节点,查一次数据可能需要全表扫描,极端情况如图:

在这里插入图片描述
可以看到,这种极端情况下,二叉树就退还成了一个链表,查找还是需要进行全表扫描。
因此,二叉树不太适合做索引。

平衡二叉树

平衡二叉树(Balanced Binary Tree)又被称为AVL树,具有如下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
在这里插入图片描述
这样看,平衡二叉树查找一个数据只需要O(log2n)就可以了,比如上面我们要查询2,只需要3次查找就行了,但是平衡二叉树的每个节点只能存储一行数据,但是看上面的平衡二叉树,我们每次往下一层查询数据相当于是进行了一次磁盘IO,如果我们要存储100W条数据,这个树的深度会很深,这样需要进行多次磁盘IO,而磁盘IO尤其是随机读的话特别耗时间。因此普通的平衡二叉树无法满足在大数量情况下作为索引来进行查询。

B+Tree

基于普通平衡二叉树的问题,人们在其上面提出了B+树的概念,既然树太深了不好磁盘IO,那我们想办法减少树的深度,那只能在每个节点多存储一些记录。
而正好,MySQL中采用页存储,每个页中能够存储很多的数据。
M有SQL中B+索引,非叶子节点存储的是索引值以及该索引所在范围区间对应的子节点地址,,这些索引都是排好序的,叶子节点中存储的是索引和记录值,同时叶子节点会指向下一个节点,在叶子节点形成一个链表
在这里插入图片描述
上图简单画了一个层高为3的B+Tree
B+索引中,每个非叶子节点,维护了一个该节点下面每个子节点的索引值的范围,比如节点A维护了(1,7,12,18,28)这几个节点,那么1这个索引下面对应节点里面维护的索引的范围是[1,7),同理7这个索引下面对应的节点里面维护的是[7,12)的范围。
当我们要查询10这个记录的时候,10这个索引在[1,18)之间,去1对应的节点查找,[9,13)这个范围符合要求,因此去9的子节点查找,找到10对应的记录。
MySQL中这个根节点页面是常驻内存的,当我们需要查找某个记录的时候,首先通过根节点二分查找,判断在哪个范围,然后找到对应范围的子节点,这里面维护了一个到该节点的指针,然后进行一次IO将该节点读如到内存中,这时候对该节点里面的数据再次进行二分查找,找到该索引对应下一层级节点,然后在重复上面步骤,直到读到叶子节点,索引,这个三层节点的B+Tree只需要3次IO就能找到对应记录

MySQL中索引的每个节点都是按照页存储的(不区分叶子节点和非叶子节点,页中有字段来标明这是叶子节点还是非叶子节点)

MySQL中InnoDB的B+索引主要分为两类:聚簇索引 和二级索引(或者说辅助索引)
聚簇索引一般是以主键为索引,叶子节点保存所有数据,如果没有指定该索引,MySQL默认会为我们建立该索引。
二级索引,一般非叶子节点存储的是索引的列的值,叶子结点存储的是索引列的值和对应的主键值

索引能够存储多少记录

我们假设一个页对于非叶子节点能够存储1000条记录,对于叶子节点能够存储100条记录,那么对于一个3层B+Tree,可以存储: 10001000100,一亿条数据,一般MySQL中B+Tree大概在4层,能够存储足够多的数据。

回表

一般通过二级索引查询,如果查询的列中有非索引列,那么还需要回表,也就是先通过二级索引查询到对应的主键值,然后在通过该主键去聚簇索引中查找对应的记录,这个过程中如果需要回表的记录比较多,则性能比较低,因此尽量将需要查询的列放在索引中,避免回表(当然不提倡将列都放到索引中,这需要一个度

B+树索引生效的条件

我们假设如下表:

create table test(
id			int primary key auto_increment,
name		varhcar(20) not null,
sex			varhcar(5),
address		varchar(50),
birthday	varhcar(20),
tel			varchar(20)
);
ALTER TABLE test ADD INDEX idx_tel(tel);
ALTER TABLE test ADD INDEX idx_name_birthday(name,birthday);
ALTER TABLE test ADD INDEX idx_name_tel_birthday(name,tel,birthday);

我们进行查询的时候,并不是所有的情况下都会走索引,一般走索引满足如下条件

  • 等值匹配: 比如:
select * from test where id=1
select * from test where tel='13012345678'

上面两个都会走索引,第一条会走聚簇索引,第二条会走辅助索引。

  • 列前缀匹配
select * from test where tel like '130123%'

这个时候会走idx_tel这个索引,在B+Tree索引中,索引是会进行排序的,当用前缀匹配的时候,对于字符串排序就是挨个比较字符串的大小,给定前缀是能够在一定范围内找到记录的,但是如果不是前缀则不行:

select * from test where tel like '%1234%'

这时候是没法走索引的,因为在排好序的索引中是无法准确定位到的。

  • 左边列匹配
select * from test where name ='leo'

这个时候是可以走idx_name_birthday这个索引的,mysql中的联合索引实际上是先按照做左边列排序,左边列相同的情况下在按右边列排序,上面这种情况,idx_name_birthday这个索引是先按照name排序的,当name相同之后在按照birthday进行排序的,这时候我们用name在idx_name_birthday能够很快找到对应的一个记录范围。
而下面这个是如法走索引的:

select * from test where birthday='2020-11-01'

这个是没法走索引的,因为和它相关的是索引idx_name_birthday,而他的索引列顺序为:(name,birthday),这个时候通过通过birthday并不能查找到相关位置,必须先通过name或者name+birthday进行查找

  • 范围值匹配
select * from test where id > 1 and id <10

前面我们说了,B+Tree的索引是拍好序的,根据查询的范围,我们能够很轻松的定位到索引中对应的范围记录。

  • 精确匹配一列并范围匹配
select * from test where name='leo' and birthday>'2020-11-01'

这个是可以走idx_name_birthday索引的,先根据name定位到记录范围,在通过birthday筛选
另外通过这里的分析我们应该知道,在索引列上使用函数也是无法使用索引的

索引的排序

默认情况下我们输出的顺序是按照索引排序的,

select * from test 

这时候默认就是按照聚簇索引排序,如果查询用到联合索引,输出的结果中排序,比如:

select * from test where name='leo' order by name,birthday

这时候是会用到idx_name_birthday这个索引,并且输出的结果是按照索引排序的,但是下面无法使用索引排序:

select * from test where name='leo' order by birthday,name

因为idx_name_birthday助攻索引排序,是先按照name排序,然后按照birthday排序的。
另外:

select * from test where name='leo' order by  name desc, birthday asc

这时候也是无法使用索引排序的,因为name和birthday 排序的方式不一样。

下面这个也是无法用到索引排序的:

select * from test where sex='M' order by  name ,birthday

这时候不会走idx_name_birthday这个索引,无法用到idx_name_birthday的索引进行排序。

Guess you like

Origin blog.csdn.net/LeoHan163/article/details/121272187