09.索引结构
基础:
- 索引中的数据页的编号不一定连续,也就是说这些页在存储空间里可能并不挨着
- B+树中
相同层次
的数据页通过页结构的File Header 中的FIL_PAGE_PREV和FIL_PAGE_NEXT,组成双向循环链表 - 每个数据页内部会维护
页目录
,通过分组的方式将所有用户记录分组 - 每个数据页内部记录之间通过next_record连接,next_record记录的是下一个记录的偏移位置
- 存储用户记录的页和存储索引的页结构FIL_PAGE_TYPE都是”FIL_PAGE_INDEX”
- 存储索引的页里面的记录叫做
目录项记录
,即索引的非叶子结点 - 存储用户记录的页里面的记录做
用户记录
,即索引的叶子节点 - 存储
目录项记录
页中的主键值最小的那个记录的min_rec_mask
值为1
索引页的组织结构:

- 从图中可以看出:
- 每个索引页最多存储4个,叶子节点最多存储3个记录(不包含infimum、supremum)
- 索引页比数据页存的记录肯定要多很多
- 数据插入:
- 先找到最底层的复合条件的数据页,如果有空间,直接插入;如果没有空间,此时会发生页分裂。
- 页分裂过程:
- 创建新页:InnoDB会创建一个新的叶子节点页。
- 重新分配记录:将当前页中的一半记录移动到新页中,确保两个页的空间使用均衡。
- 更新双向循环指针:同层级前后页的指针进行调整
- 更新父节点:在父节点中插入一个新的目录项记录,指向新创建的页,并更新原有页的目录项记录。
- 递归处理:如果父节点也已满,页分裂会递归向上进行,直到根节点。如果根节点也需要分裂,会创建一个新的根节点,树的高度增加。
- 页分裂举例:
- 插入(101,0,x)
- 定位到应该放在页9 中,但页9 满了,发生页分裂,将(100,9,x)和(101,0,x)放到新申请的页38中(同时需要更新页9 的NEXT和页20 的PREV指针)
- 更新父节点页30,插入一条目录项记录(100,38),因为父节点也满了,此时父节点也进行页分裂,申请新 页49,将一半的目录项记录移动到新页中
- 新的页49此时含有(100,38)、(209,20),更新页30的NEXT指针和页32的PREV指针
叶子结点的页内结构:
- record_type=0
- 存储:索引列、用户数据,构成一条用户记录
非叶子结点的页内结构:
- record_type = 1
- 存储:索引列、页中最小记录的主键值、对应主键所在页号,构成一条目录项记录
聚簇索引:
-
横向:根据
主键值的大小
进行页内记录排序、页间排序 1.
页内的记录是按照主键大小
顺序排成单向链表
2.
存放用户记录的页
根据页中用户记录的主键大小
顺序排成一个双向链表
3.
存放目录项记录的页
分为不同的层次,在同一层次中的页
也是根据页中目录项记录的主键大小
顺序排成一个双向链表
- 竖向: 1.
目录项记录页
中记录下一层级的目录项记录页号
或者用户记录的页号
-
聚簇索引的叶子节点存储的是完整的用户记录
,包括隐藏列
- 聚簇索引的非叶子结点存储的是主键+页号
-
聚簇索引就是innodb数据的存储方式,如果没有定义主键,则会使用其他的唯一非空索引
或者row_id
创建聚簇索引
二级索引(索引列c2):
-
横向:根据
索引列的值大小
进行页内记录排序、页间排序 1.
页内的记录是按照索引列c2
顺序排成单向链表
2.
存放用户记录的页
根据页内用户记录的索引列c2
顺序排成一个双向链表
3.
存放目录项记录的页
分为不同的层次,在同一层次中的页
也是根据页中目录项记录的索引列c2
顺序排成一个双向链表
- 竖向: 1.
目录项记录页
中记录下一层级的目录项记录页号
或者含有主键信息的页号
- 二级索引的叶子节点存储的是索引列c2
和主键值
-
二级索引的非叶子结点中存储的是索引列c2+页号
(实际上如果是非唯一索引,为了保证每个目录项记录的唯一性,还会存储主键值
)
-
通过二级索引
找到符合条件的主键值
,需要再到聚簇索引
中继续查找用户记录,这个过程称为回表
联合索引(索引列c2、c3):
- 联合索引也是二级索引
- 横向:根据
索引列的值大小
进行页内记录排序、页间排序- 记录先按照
c2
列的值进行排序,如果记录的c2
列相同,则按照c3
列的值进行排序 - 其他同二级索引
- 记录先按照
- 竖向:
目录项记录页
中记录下一层级的目录项记录页号
或者含主键信息的页号
- 连接规则同二级索引
- 叶子节点节点存储的是
c2
、c3
和主键c1
- 非叶子结点中存储的是
索引列c2、c3 + 页号
InnoDB的B+树索引的注意事项:
- 根页面创建后万年不变:
- B+树创建过程:
- 每当为某个表创建一个
B+
树索引(聚簇索引不一定需要人为创建)的时候,都会为这个索引创建一个根节点
页 - 最开始表中没有数据的时候,每个
B+
树索引对应的根节点
页既没有用户记录,也没有目录项记录 - 随后向表中插入用户记录时,先把用户记录存储到这个
根节点
页 - 当
根节点
中的可用空间Free space
用完,如果继续插入记录,此时会发生页分裂
,将根节点
中的所有记录复制到一个新分配的页。这时新插入的记录根据索引列的大小(聚簇索引的主键,二级索引的索引列)就会被分配到新的页中,而根节点
便升级为存储目录项记录的页 - 特别注意的是:一个B+树索引的根节点自诞生之日起,便不会再移动。只要我们对某个表建立一个索引,那么它的根节点的页号便会被记录数据字典,然后凡是InnoDB存储引擎需要用到这个索引的时候,都会从
数据字典
取出根节点的页号,从而来访问这个索引。
- 每当为某个表创建一个
- B+树创建过程:
- 内节点(非叶子结点)中的目录项记录需要唯一性保证:
- 为了确保去除
页号
后的唯一性,非唯一索引的目录项记录
存储了索引列
、主键值、页号
- 如果不唯一,则可能会存在插入记录时多个页都可选:
- 页3实际存储的是(1,1,4)、(1,7,5)
- 为了确保去除
- 一个页面最少存储2条记录:
关于MyISAM:
- 索引特点:
- MyISAM的索引方案也使用
B+树
,但是却将索引和数据分开存储
,属于非聚簇索引 - 把用户记录
按照记录的插入顺序
单独存储在一个文件中,称之为数据文件
,文件包含行号
+用户数据
;- 数据文件都是时间序,区别于innodb按照主键排序
- 把索引信息存储到另一个称为
索引文件
的文件中,文件包含主键值 + 行号/数据偏移位置
- MyISAM的索引方案也使用
- 查找过程:
- 先通过
索引文件
中的索引找到对应的行号
,再通过行号
去数据文件
找对应的记录 MyISAM
中建立的索引相当于全部都是二级索引
- 先通过
- MyISAM 数据文件:
- 对比:
- 回表:
- Innodb从
二级索引
找到主键
,再通过聚簇索引
找到对应用户记录 - MyISAM从
索引文件
找到记录的偏移位置,再通过数据文件
找到对应用户记录
- Innodb从
- 数据:
- InnoDB中的索引即数据,数据即索引(
聚簇索引
) - 而MyISAM中却是索引是索引、数据是数据
- InnoDB中的索引即数据,数据即索引(
- 回表:
- 适用于读多写少的场合、全文索引支持、支持压缩(偏向于ck)
其他存储引擎:
引擎 | 特点 | 适用场景 |
---|---|---|
Innodb | 支持事务 | 最常用 |
Myisam | 支持压缩和全文索引 | 读多写少的大数据场合,更新效率低 |
Memory | ||
RocksDB | 高吞吐量和低延迟的读写操作,适合高并发和高性能 | 高性能键值存储的场景 |
TokuDB | 高效的写入和压缩性能 | 适用于大数据量、高写入负载的应用 |
NDB | 用于MySQL Cluster,提供高可用性和高可扩展性 | 高可用性和高扩展性的分布式系统 |
Archive |
本文作者:navyum
本文链接:https://www.cnblogs.com/navyum/p/18509416
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步