MySQL InnoDB 存储原理
简介
- mysql InnoDB 存储引擎
目录结构
mysql InnoDB存储引擎的数据以
目录文件
形式存储在磁盘中;每个db 都会在
/var/lib/mysql/
目录里面创建一个以 db 为名的目录;每个db目录下包含:
- db.opt:db选项,记录当前数据库db的默认字符集和字符校验规则;
- table1.frm:表table1结构文件,保存每个表的元数据信息的,主要包含表结构定义;
- table1.ibd:表table1数据文件,表数据既可以存在共享表空间文件(文件名:ibdata1)里,也可以存放在独占表空间文件(文件名:表名字.ibd)。这个行为是由参数 innodb_file_per_table 控制的,若设置了参数 innodb_file_per_table 为 1,则会将存储的数据、索引等信息单独存储在一个独占表空间,从 MySQL 5.6.6 版本开始,它的默认值就是 1 了,因此从这个版本之后, MySQL 中每一张表的数据都存放在一个独立的 .ibd 文件。
|
|
表数据文件(.idb)
表数据文件(.ibd)又叫表空间文件;
表数据既可以存在共享表空间文件(库名/ibdata1)里,也可以放在单独的表空间文件(
库名/表名.ibd
)中;表空间文件从大到小由: 段(segment), 区(extent), 页(page), 行(row)4级组成;
行(row):
- innodb中表记录的每行数据存储为一行;
- 行是innodb数据存储基本单元;
- 一行记录除了 TEXT、BLOBs 类型的列,最大为 65535(64K) 字节;
页(page):
- 页是innodb的读写单元,innodb每次读写都是一个完整的page;
- 每个页可以包含多个row;
- 每个页默认大小16KB;
- 单个页在磁盘上是连续的, 多个页之间通过双向链表组织起来,逻辑上连续,物理上可能不连续;
- 页分为:
- 数据页:包含row记录, B+树;
- undo 日志页:;
- 溢出页
区(extent):
- 当表中数据量大时,为某个索引分配空间不按照页为单位分配了,而是按照区(extent)为单位分配;
- 每个区中包含一系列相邻的页,使用顺序 I/O 了;
- 每个区的大小为 1MB;
段(segment):
段分为:
- 索引段:存放 B + 树的非叶子节点的区的集合;
- 数据段:存放 B + 树的叶子节点的区的集合;
- 回滚段:存放的是回滚数据的区的集合, MVCC 利用了回滚段实现了多版本查询数据。
每个段又多个区(extent)组成;
所有段共同组成表空间文件;
行存储结构
InnoDB 的表行存储提供有 4 种行格式:
- Redundant: MySQL 5.0 版本之前用的行格式,不是一种紧凑的行格式;
- Compact:一种紧凑的行格式,可以让一个数据页中可以存放更多的行记录,是MySQL 5.1 版本之后的默认行格式;
- Dynamic:基于Compact格式改进, MySQL5.7 版本之后的默认使用格式;
- Compresse:
Compact格式行结构
一条记录(行)分为两部分:
- 记录的额外信息;
- 记录的真实数据;
记录的额外信息
变长字段长度列表
- 记录varchar, text, blob等变长列的字节长度;
- 按照列的顺序逆序存放;
- 不需要保存值为 NULL 的变长字段的长度;
- 逆序存放是为了从记录头信息开始,位置靠前的记录的真实数据和数据对应的字段长度信息可以同时在一个 CPU Cache Line,提高效率;
NULL 值列表
- 记录可能会存储 NULL的列是否是NULL值;
- 每个列对应一个二进制位(bit),1代表NULL,0为非NULL;
- NULL 值列表必须用整数个字节的位表示,小于9个NULL列,用1个字节,否则用多个字节;
- 当所有字段都定义成 NOT NULL 时,不需要NULL值列表;
- NULL值列表也是和列顺序反向存放的;
记录头信息
- delete_mask:标识此条数据是否被删除,执行 detele 删除记录的时候,并不会真正的删除记录,只是将该条记录的 delete_mask 标记为 1;
- next_record:下一条记录的位置。指向的是下一条记录的「记录头信息」和「真实数据」之间的位置;
- record_type:表示当前记录的类型。
- 0:表示普通记录;
- 1:表示B+树非叶子节点记录;
- 2:表示最小记录;
- 3:表示最大记录;
记录的真实数据
记录的真实数据除了各列真实值外, 还有三个隐藏字段, 分别如下:
row_id:行ID
- 建表时未指定主键和唯一约束列,则innodb将自动添加row_id,否则没有row_id;
- row_id不是必需的,有的话占用 6 个字节;
trx_id:事务id
- 表示这条记录是由哪个事务生成的;
- trx_id必需,占用 6 个字节;
roll_pointer:回滚指针
- 记录上一个版本的指针;
- roll_pointer 是必需的,占用 7 个字节。
行溢出
- 如果一个数据页存不了一条记录,InnoDB 存储引擎会自动将溢出的数据存放到
溢出页
中; - 发生行溢出时,
- Compact格式的行记录会在记录的真实数据处保存该列的一部分数据,而把剩余的数据放在
溢出页
中,然后真实数据处用 20 字节存储指向溢出页的地址
; - Compressed 和 Dynamic 这两个行格式的行记录会把所有数据放在
溢出页
中,而真实数据处只存只用 20 字节存储指向溢出页的地址
;
- Compact格式的行记录会在记录的真实数据处保存该列的一部分数据,而把剩余的数据放在
数据页结构
MySQL InnoDB存储引擎的数据是按数据页
为单位来读写的, 数据页的默认大小是 16KB, 数据页包括7个部分:
File Header(文件头):记录页信息, File Header 中有两个指针,分别指向上一个数据页和下一个数据页;
Page Hader(页头):页状态信息;
最大最小记录:页中最大记录,最小行记录;
User Records:一系列数据行记录;
- 记录按照「主键」顺序组成单向链表;
FreeSpace: 页中还未被行记录使用的空闲空间;
Page Directory:页目录,记录的索引作用;
FileTailer:校验页是否完整;
多个数据页通过FileHeader中的2个指针组成双向链表。
页目录
数据页中的记录按照主键
顺序组成单向链表, 直接使用链表会导致查询速度缓慢, 所以设置了页目录, 起到记录的索引, 加快查找速度的作用. 页目录创建的过程如下:
- 将所有的记录划分成几个组,这些记录包括最小记录和最大记录,但不包括标记为“已删除”的记录;
- 每个记录组的最后一条记录就是组内最大的那条记录,并且最后一条记录的头信息中会存储该组一共有多少条记录,作为 n_owned 字段(上图中粉红色字段)
- 页目录用来存储每组最后一条记录的地址偏移量,这些地址偏移量会按照先后顺序存储起来,每组的地址偏移量也被称之为槽(slot),每个槽相当于指针指向了不同组的最后一个记录。
B+树结构
- 索引分为聚簇索引,非聚簇索引;
- 表的数据都是存放在聚簇索引的叶子节点里;
- 非聚簇索引的叶子节点存放的是主键值,而不是实际数据;