概述
在MYSQL中的B-TREE索引的节点都是一个一个的数据页,数据页也是磁盘和内存交互的基本单位,数据库在读取数据时无论是读取一行还是多行其实都是将这些行所在的数据页加载到内存中(如果一次读取只获取一行数据,那么读取多条数据则需要多次IO,效率过低)。在InnoDB中每个数据页的默认大小为16KB。
页结构
- 文件头:描述页的信息,38个字节,文件头里包含了页码信息、上一页的地址和下一页的地址、还有校验和信息用于和文件尾的校验和信息一起来保证数据同步时的完整性。当内存中的数据页经历过增、删、改的操作时内存中数据页的文件头的校验和信息会重新计算获取一个值,mysql在内存中同步数据到硬盘上时,会先从文件头刷起,先覆盖新的校验和信息,如果刷到中间时服务器宕机了,那么重启后就会发现文件头和文件尾的校验和信息不相等则证明当前数据页没有同步完成
- 页号
- 上一页地址
- 下一页地址
- 页的类型(系统页、日志页、数据页)
- 校验和
- 日志序列位置
- 文件尾
- 校验和
- 日志序列位置
- 用户记录:默认使用compact行格式记录数据
compact行格式结构:- 变长字段长度列表:对于变长的数据类型,比如VARCHAR(M)、VARBINARY(M)、各种TEXT类型,各种BLOB类型,我们也可以把拥有这些数据类型的列称为变长字段,变长字段中存储多少字节的数据是不固定的,所以我们在存储真实数据的时候需要顺便把这些数据占用的字节数也存起来,这样才不至于把MySQL服务器搞懵,所以这些变长字段占用的存储空间分为两部分:真正的数据内容和占用的字节数。
- NULL值列表
- 记录头信息(5字节)
- delete_mask:使用0和1记录当前数据是否被删除。标记为1表示数据已被删除,当插入数据时,当前记录的空间可以被复用
- min_rec_mask:只有非叶子节点的数据页中最小记录的值为1
- record_type:当前记录的类型,0表示普通记录,1表示非叶子节点记录,2表示最小记录,3表示最大记录
- heap_no:当前记录在本页中的位置。用户记录的值是从2开始的,因为MySQL会给每个页里插入两条虚拟记录,一个最小记录(heap_no为0),一个最大记录(heap_no为1)
- n_owned:页目录中每个组最后一条记录的n_owned记录了当前组中数据的条数
- next_record:记录了到下一条数据的偏移量。最后一条用户记录的next_record指向最大记录,最小记录的next_record指向第一条用户记录
- 真实数据
- 默认隐藏列信息:MySQL会为每个记录默认的添加一些列(也称为隐藏列),实际上这几个列的真正名称其实是:DB_ROW_ID(没有设置主键的表会用row_id作为默认主键)、DB_TRX_ID、DB_ROLL_PTR。
- 最大最小记录:和用户记录结构相同只不过在用户记录存放真实数据的位置,最大最小记录存放的是固定的内容Infimum和Supermum
- 空闲空间:最开始没有用户记录则只有空闲空间,随着用户记录越来越多,则空闲空间就越来越小,当空闲空间没有时,则当前数据页的空间被使用完,如果此时需要插入新纪录,则需要申请新的数据页
- 页目录:将所有的记录分成几个组(包括用户记录和最大最小记录,但是不包括被删除的数据即delete_mask为1的数据),每组4-8条数据,页目录就是用来存储每组最后一条记录的地址偏移量的,这些地址会按照先后顺序存储起来,每组的地址偏移量也被称之为槽,每个槽指向了不同组的最后一条记录。查询时就使用二分法来快速定位到数据
- 页头
- 槽数量
- 记录数量(包括最大最小记录和标记为删除的记录)
从数据页看B+树如何查询
如果通过B+树的索引查询行记录,首先是从B+树的根节点开始,逐层检索,直到找到叶子节点,也就是找到对应的数据页为止,将数据页加载到内存中,页目录中的槽采用二分查找的方式先找到一个粗略的记录分组,然后再在分组中通过链表遍历的方式查找记录。