MySQL存储结构
InnoDB存储
表->段->区->页->行
在数据库中,不论读哪一行数据,还是读多行数据,都是将这些行所在的页进行加载。也就是存储空间的基本单位就是页。
一个页就是一颗B+树的节点,数据库I/O操作的最小单位是页,与数据库相关的内容都会存储在页的结构里。
Table Space
表空间,数据存储为 三个段(segment)
Segment
Leaf node segment
叶结点段
存储数据记录
Non-Leaf node segment
非叶子结点段
存储索引:主键索引,辅助索引
Rollback segment
回滚段
存储事务回滚数据(维护不同事务的独立视图,记录事务所有过程)
Page (Special)
当表数据非常小时,实际上没有Extent
因为表数据小时 (小于1M) 生成Extent时会造成空间浪费,因此在这种情况下会直接将Page存在Segment中。
通常32个Page,当32个Page塞满时,开始扩增第一个Extent
Extent
一个表区分为三个段,每段内部结构类似,内部分为若干个 区(Extent) ,每个区默认大小1MB的
表数据容量扩增基本单位就是 Extent
Page
每个 区(Extent) 中包含若干个 页(Page),每个Page默认大小16K,因此一个Extent默认下为64个Page,即默认情况下每次扩增Extent增加64个Page
Page 中存储记录,因为每条记录不等长,因此16K可以存放若干条记录
当表记录非常少或零碎数据时,Page会直接在 段(Segment) 中生成,而非生成一个Page,节约空间占用(一般32个Page)
整个MySQL是以 Page 作为单位管理的
MySQL设计是读取数据是以磁碟结构为基础设计的,当只读取一条时磁头以Row为单位移动不划算,因此以一个Page(16K)为基准一次读取,再筛选需要的数据
Page Header
- 控制信息
Row
数据记录
理想状态下是主键顺序存储,但主键可以不按顺序插入数据
指针
为了保证顺序,每一行都有一个指针(单向链表)
Page Directory
Slot | Slot | Slot | Slot | Slot | Slot | Slot |
---|---|---|---|---|---|---|
1 | 指向页中的行地址 | 2,3 | 指向页中的行地址 | 4 | 指向页中的行地址 | … |
严格按照主键递增顺序存储Directory
当读取Page时,即可获取到Page Directory,迅速获取Row数据
获取方式:二分法
每个Slot可能存储不止一条Row
若只有一条Row,则查询性能高,但会导致Page Directory过长,若多条数据则寻道时间长,易与业务性能要求冲突,需根据业务需求设计Row大小 (建议Page使用默认值16K)
Row
数据记录
页合并与分裂
页分裂
页可能填充至100%,在页填满了之后,下一页会继续接管新的记录。但如果没有足够空间去容纳新(或更新)的记录时,需在页中间插入数据,此时会触发页分裂。
InnoDB的做法:
- 创建新页
- 判断当前页可以从哪里进行分裂(记录行层面)
- 移动记录行
- 重新定义页之间的关系
页合并
当你删了一行记录时,实际上记录并没有被物理删除,记录被标记(flaged)为删除并且它的空间变得允许被其他记录声明使用。
当页中删除的记录达到MERGE_THRESHOLD
(默认页体积的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用。
页合并发生在删除或更新操作中,关联到当前页的相邻页。如果页合并成功,在INFOMATION_SCHEMA.INNODB_METRICS
中的index_page_merge_successful
将会增加。
因此不建议使用非主键记录,因为乱序问题可能导致页分裂频繁。同时不建议使用物理删除,因为会导致页合并(推荐使用逻辑删除,若逻辑删除导致空间不足,应申请窗口维护期统一物理删除数据)。