MySQL索引(二)

87 阅读5分钟

索引的底层实现原理

数据库索引是存储在磁盘上的,当数据量大时,就不能把整个索引全部夹在到内存中,只能逐一加载每一个磁盘块(对应索引树的节点),索引树越低,磁盘I/O次数就越少。

MySQL支持两种索引,一种B-树索引,一种是哈希索引。

MySQL默认存储引擎InnoDB是基于B-树(但实际上MySQL采用的是B+树结构)的索引结构。

B-树是一种m阶平衡树,叶子节点都在同一层,由于每一个节点存储的数据量比较大,索引整个B-树的层数是很低的,基本上不超过三层。

由于磁盘读取也是按照block块操作的(内存是按page页面操作的),因此B-树的节点大小一般设置为和磁盘块大小一致,这样一个B-树节点,就可以通过一次磁盘I/O把一个磁盘块的数据全部存储下来,所以当使用B-树存储索引的时候,磁盘I/O操作次数是最少的(MySQL的读写效率,主要集中在磁盘I/O上)。

B-树

image.png

当我们想要查询数据的时候,MySQL server会把在磁盘中的索引读到内存中,会花费磁盘I/O,也就是说读一次索引就会花费一次磁盘I/O。

select * from student where uid = 1;

其中uid为主键索引,MySQL server做的事:

通过uid索引,请求存储引擎->花费磁盘I/O读索引文件->读到内存上,用索引的数据构建B-树加速搜索。

但是B-树也有他的缺点:

  • 每个节点中有key,也有data(图中只在叶子节点中标注了data),但是每一个节点的存储空间是有限的,如果data数据较大时会导致每个节点能存储的可以值很小
  • 当存储的数据量很大时同样会导致B-树的高度较大,磁盘I/O次数花费增大,效率较低

B+树

B+树简单来说就是B-树的PLUS版,B+树的详细内容属于数据结构的知识,这里不多做讲解。

但是我们可以简单理解:B+树的每一个非叶子节点,只存放key,不储存data。这样做的好处就是一个节点存放的key值更多一些,层数会更低一些,搜索的效率会更好一些。

那么MySQL为什么最终要采用B+树存储索引结构呢:

  • B-树的每一个节点,存了关键字和对应的数据地址,而B+树的非叶子节点之村关键字,不存放地数据地址。因此B+树的每一个非叶子节点存储的关键字是远远多于B-树的,因此从树的高度上来说,B+树的高度要小于B-树,使用磁盘I/O次数少,因此查询会更快一些。
  • B-树由于每个节点都存储关键字和数据,因此离根节点近的数据查询的就快,离根节点远的数据查询的就慢;B+树所有的数据都存在叶子节点上,因此B+树上搜索关键字,找到对应数据的时间是比较平均的,没有快慢之分。
  • 在B-树上如果做区间查找,遍历的节点是非常多的;B+树所有叶子节点被链接成了有序链表,因此做整表遍历和区间查找是非常容易的。

InnoDB的主键和二级索引树

我们上面提到,MySQL默认的存储引擎是InnoDB,对于InnoDB来说,数据和索引存放在同一文件中:

对于我们的user表:

user.frm文件 --> 存储表的结构

user.ibd文件 --> 存储表的索引和数据

我们在搜索的时候,引出以下场景:

场景一:uid是主键

select * from user; 
# 搜索的是整个索引树(索引+数据),即搜索整个叶子节点所构成的有序链表(B+树)

select * from user where id = 2;
# 等值查询,从根节点开始二分搜索
select * from user where id < 2;
# 范围查询,在有序链表中直接搜索
select * from user where name = wang wei;
# 整表查询,在有序链表中直接搜索

场景二:id是主键,name创建了二级索引

用name构建的二级索引树,节点存放的是字段name和id的值(二级索引+主键索引),即key是二级索引的值,data是所在记录行的主键值。

select name from user where name = 'wang wei';
# 因为select后面为name,所以直接搜索二级索引树的key值就可以找到了
select name,id from user where name = 'wang wei';
# 和上面是一样的,因为索引树节点保存的值有name和id

select * form user where name = 'wang wei';
# 搜索name的二级索引树,找到wang wei对应的主键值id = 2
# 再拿id = 2回表在主键索引树上搜索id那一行的记录

还有一种order by特殊情况:

select * from user where age = 22 order by name;

过滤条件是age,所以我们删除name索引,创建age索引

我们看到age可以命中索引了,但是最后一行Extra字段出现Using filesort,我们在上一篇文章中提到,出现Using filesort的排序无法用到索引,这种情况我们就要使用联合索引来优化。

创建age_name_idx联合索引,解决问题。