前言
最近在学MySQL
,决定记录一下,能写多少写多少,不定时更新,加油。
正文
分几个部分来吧,大致如下:
字符集与比较规则
行格式与数据页
InnoDB
索引访问方法与连接
explain 与 子查询优化
redo
与undo
日志MVCC
与 锁
本文为第二部分 第二节 数据页
上回说的单条记录没有挑战,下面开启多条记录的处理--数据页
数据页的秘密其实在前面我们说到的记录头的几个属性里面已经说了一点了,比如分组、单链表、最小记录、最大记录、B+树索引、叶子节点等。
那么,让我们继续“脱衣服”~
数据页结构
名称 | 中文名 | 占用空间大小 | 简单描述 |
---|---|---|---|
File Header |
文件头部 | 38字节 | 页的一些通用信息 |
Page Header |
页面头部 | 56字节 | 数据页专有的一些信息 |
Infimum + Supremum
|
最小记录和最大记录 | 26字节 | 两个虚拟的行记录 |
User Records |
用户记录 | 不确定 | 实际存储的行记录内容 |
Free Space |
空闲空间 | 不确定 | 页中尚未使用的空间 |
Page Directory |
页面目录 | 不确定 | 页中的某些记录的相对位置 |
File Trailer |
文件尾部 | 8字节 | 校验页是否完整 |
简单的介绍一下这些属性中的重要的东西
Infimum
+ Supremum
- 这个值位于整个页面的第三部分
- 分别是最小记录和最大记录,属于MySQL为每个页添加的
虚拟记录
- 由五个字节的
记录头
和 八个字节的 值(分别是单词Infimum
和Supremum
)组成 - 最小记录的记录头中
heap_no
为0 - 最大记录的记录头中
heap_no
为1 - 也就是说正式记录中的
heap_no
属性从2开始 - 最小记录的record_type 是2
- 最大记录的record_type 是3
- 最小记录是页中单链表的头结点
- 最大记录是页中单链表的尾结点
Page Directory
- 这个页目录里面存的是一个一个的槽(
slot
) - 每个槽指向组内最大记录的地址偏移量(我理解为页内偏移量)
- 这个数据结构是数组,按主键值从小到大排列
- 这种结构注定了查询最快的方式是二分法,时间复杂度O(lgN)
-
页分裂
(前面说过) 时会增加一个槽
User Records
和 Free Space
- 完全空闲的页是没有
User Records
部分的 - 插入数据时,从
Free Space
分配空间给User Records
,直到Free Space
没有空间或空间不够分配新的记录,这时需要申请新的页
Page Header
名称 | 占用空间大小 | 描述 |
---|---|---|
PAGE_N_DIR_SLOTS |
2字节 | 在页目录中的槽数量 |
PAGE_HEAP_TOP |
2字节 | 还未使用的空间最小地址,也就是说从该地址之后就是Free Space |
PAGE_N_HEAP |
2字节 | 本页中的记录的数量(包括最小和最大记录以及标记为删除的记录) |
PAGE_FREE |
2字节 | 第一个已经标记为删除的记录地址(各个已删除的记录通过next_record也会组成一个单链表,这个单链表中的记录可以被重新利用) |
PAGE_GARBAGE |
2字节 | 已删除记录占用的字节数 |
PAGE_LAST_INSERT |
2字节 | 最后插入记录的位置 |
PAGE_DIRECTION |
2字节 | 记录插入的方向 |
PAGE_N_DIRECTION |
2字节 | 一个方向连续插入的记录数量 |
PAGE_N_RECS |
2字节 | 该页中记录的数量(不包括最小和最大记录以及被标记为删除的记录) |
PAGE_MAX_TRX_ID |
8字节 | 修改当前页的最大事务ID,该值仅在二级索引中定义 |
PAGE_LEVEL |
2字节 | 当前页在B+树中所处的层级 |
PAGE_INDEX_ID |
8字节 | 索引ID,表示当前页属于哪个索引 |
PAGE_BTR_SEG_LEAF |
10字节 | B+树叶子段的头部信息,仅在B+树的Root页定义 |
PAGE_BTR_SEG_TOP |
10字节 | B+树非叶子段的头部信息,仅在B+树的Root页定义 |
1.PAGE_GARBAGE
- 在页面空间不够时,会尝试将所需空间与这个值比较,如果够,那么将记录移到临时表中,再重新插回来(记录逐条新增是不会产生碎片空间的),但很明显,这很耗性能
-
PAGE_BTR_SEG_LEAF
和PAGE_BTR_SEG_TOP
- 后面会说到段的概念,一个索引会有两个段,一个叶子节点段,一个非叶子节点段,这两个属性分别代表一个
Segment Header
,代表了哪个表空间哪个页面的哪个INODE Entry
(表空间号 + 页面号 + 偏移量值)
File Header
名称 | 占用空间大小 | 描述 |
---|---|---|
FIL_PAGE_SPACE_OR_CHKSUM |
4字节 | 页的校验和(checksum值) |
FIL_PAGE_OFFSET |
4字节 | 页号 |
FIL_PAGE_PREV |
4字节 | 上一个页的页号 |
FIL_PAGE_NEXT |
4字节 | 下一个页的页号 |
FIL_PAGE_LSN |
8字节 | 页面被最后修改时对应的日志序列位置(英文名是:Log Sequence Number) |
FIL_PAGE_TYPE |
2字节 | 该页的类型 |
FIL_PAGE_FILE_FLUSH_LSN |
8字节 | 仅在系统表空间的一个页中定义,代表文件至少被刷新到了对应的LSN值 |
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID |
4字节 | 页属于哪个表空间 |
这个属性里面的值就说三个:
FIL_PAGE_OFFSET
- 每个表空间的页号唯一,四个字节足够用了
-
FIL_PAGE_PREV
和FIL_PAGE_NEXT
- 上一页和下一页,诶哟,这不是双向链表啊,那所有的页加起来组成了一个双向链表哇,好玩吧~
FIL_PAGE_TYPE
类型名称 | 十六进制 | 描述 |
---|---|---|
FIL_PAGE_TYPE_ALLOCATED |
0x0000 | 最新分配,还没使用 |
FIL_PAGE_UNDO_LOG |
0x0002 | Undo日志页 |
FIL_PAGE_INODE |
0x0003 | 段信息节点 |
FIL_PAGE_IBUF_FREE_LIST |
0x0004 | Insert Buffer空闲列表 |
FIL_PAGE_IBUF_BITMAP |
0x0005 | Insert Buffer位图 |
FIL_PAGE_TYPE_SYS |
0x0006 | 系统页 |
FIL_PAGE_TYPE_TRX_SYS |
0x0007 | 事务系统数据 |
FIL_PAGE_TYPE_FSP_HDR |
0x0008 | 表空间头部信息 |
FIL_PAGE_TYPE_XDES |
0x0009 | 扩展描述页 |
FIL_PAGE_TYPE_BLOB |
0x000A | BLOB页 |
FIL_PAGE_INDEX |
0x45BF | 索引页,也就是我们所说的数据页 |
现在只需要看这最后一个索引页,又称数据页(跟InnoDB
的特性有关)
属性介绍了一堆,还是不知道怎么查,举个栗子
- 假设一条查询语句定位到了这个页面
- 先将条件中的列(加深为
col
)与值(假设为x
)拎出来,二分法定位在哪个组。
- 假设10个槽,(0 + 9)/2 = 4, 将第四个槽的条件列的值拿出来跟
x
比较,如果x
大,那就从5~9的槽中继续二分找;如果x
小, 那就从0~4的槽中继续二分; - 直到前后两个槽相差为1,那记录就在那个大的槽里面,又每个槽其实存的是组内最大记录,那前一个槽对应记录的下一个节点就是目标槽对应组中的最小值,顺着单链表往后一个一个比就完事了,每组最多八条记录,这速度还是很快的。
没明白就多看两遍~