原文链接:关于Mysql数据存储,你了解多少?
Innodb 数据怎么存储?
Innodb 的存储结构:
- Segment:
- extent:
- page: 磁盘和内存交换的基本单位,默认 16KB
- row:
Row
记录在磁盘的存放方式也被称为「行格式」或者记录格式。行格式主要包含以下几种:
- compact
- redundant
- dynamic
- compressed
1. compact
compact 的存储结构如下:
- 变长字段长度列表:存储变长数据类型真实存储的字节长度
- null 值列表:统一管理所有可以为 null 的列,分别用 0/1 代表对应列是否为空
- 目的:节省 NULL 的存储空间
- 记录头信息:包含记录的状态、记录数、记录类型等信息
2. redundant
与 compact 相比,缺少了「变长字段列表」以及「NULL值列表」,增加了所有真实数据的偏移地址
3. dynamic
和 compact 基本相同,对于溢出页的处理会有一些差异。
溢出页:存储空间较大的列,单页无法存储,需要将部分数据存在其他页中
compact 中溢出页会先存储 768字节的数据,20个字节存储剩余数据存储页的地址。dynamic 会直接存储20字节的溢出页地址,不再额外记录一部分的数据。
4. compressed
在 dynamic 的基础上增加了压缩处理,能存储更多大长度类型的数据,但是性能并不友好。
Page
页中会有单独的信息标识当前的空闲空间,以及用户记录。当空闲不足时,再有新的记录就会申请新的页。
- Free Spage: 页中未使用的空间
- User Records: 实际存储的行记录内容
- Page Directory: 页中一些记录的相对位置
- infimun/supermun: 最 小/大 记录的位置,是虚拟的行记录
- File Header: 每一页都有一个唯一的页号,还会记录上一页和下一页的页号。页之间是一个双向链表的结构
其中 user records 中的记录按照 id 从小到大的顺序串联成了一个单向链表:
- infimun 指向 id 最小记录
- 最大记录 指向 supermun
记录中有一个 delete_mask 标记当前数据是否被删除,执行删除操作后数据会先标记,但不会立即移除,因为重新排列需要比较大的性能消耗。所有删除的数据组成了一个垃圾链表,这些占用空间在后面可能会直接被新数据覆盖
单向链表的检索效率不高,最差需要遍历页中的所有数据,因此增加了 页目录(Page Directory) 来提升效率。页目录的主要模式如下:
- 增加了一层槽的概念,每个槽之间会有不超过8条的数据,超过8条则再次拆分
- 按照主键进行排序
这样在页中查找指定主键记录的过程:
- 通过二分确认该记录所在的槽,并找到该槽所在分组中 主键 最大的记录
- 如果定位到数据所在的槽分组,则向上找一个槽位,定位到最后一行,通过 next_record 索引遍历目的槽中的记录
B+索引怎么提升检索效率?
回顾一下,innodb 数据页之间通过双向链表相连,页内数据按照「主键」排序组成单向链表,每个数据页也会维护一个「页目录」通过二分提升效率。
问题:
- 刚才聊的都是在单页中查找,那么如何找到数据所在的页?
- 页目录是针对主键的,那么查询条件不是主键怎么办?
怎么构建索引?
预设:下一个页的主键必须大于上一个页中的主键。
假设一个数据页最多有3条数据,且当前数据页中已经达到了 3 条记录的上限,新增一条记录之后会新申请一个页,但是可能会破坏页之间主键大小的预设,如下图:
此时,就需要重新移动记录,把记录4移动到页2,把记录3移动到页1,这个过程就是「页分裂」
但是实际存储时,页之间的编号并不连续,因此需要新增一个目录来记录页的位置和数据范围。如下图,目录1记录了页1的页号和最小值,目录2记录了页23的页号和最小值。我们查找数据时:
- 在目录中根据二分快速找到对应的页
- 再根据二分找到对应的槽,再去遍历数据
这个目录就叫索引。
Innodb 中的索引怎么构建?
Innodb 使用 页 来存储目录以及具体的目录项,对应的存储结构如下。其中页17存储了目录,页1、页23和页8存储了数据。
如果数据太多,单页无法存储所有的目录?
- 那就再加一页,页之间通过双向链表相连 如果数据继续增加,目录页也变得足够多?
- 那就再增加一层“目录页”的“目录页”
最终的结构如下图所示:
- 每个页节点都维护下一层页的页码和最小id,页之间的 id 有序
- 只有底层叶节点真正存储了用户数据
而这个索引就是B+树的结构,也就是我们常说的“聚簇索引”,是 innodb 自动创建的索引。那么非聚簇索引呢?
- 使用非id列进行记录和排序
- 叶节点存储的不是完整的用户记录,而是「索引列」+「主键」
索引虽好,不要贪杯
- 时间上:每次数据的变更,都需要修改各个B+树的索引,还伴随着页分裂
- 空间上:每一个索引都需要单独建立一个B+树