Innodb 逻辑存储结构
[TOC]
来源
《mysql技术内幕》
MYSQL存储引擎INNODB详解,从底层看清INNODB数据结构
重要的
- innodb B+树索引本身并不能找到具体的一条记录,能找到只是该记录所在的页。数据库把页载入到内存,然后通过page directory在进行二叉查找具体的记录。
- 数据记录在页中是以堆的形式存放的
- VARCHAR 或者 BLOB 对象大的时候,使用溢出页面存放实际的数据。
整体
表空间(tablespace)
- 不启用innodb_file_per_table
- all in 共享表空间ibdata1(也可以使用多个文件组成)
- 启用innodb_file_per_table
- 共享表空间
- undo log
- 插入缓冲索引页
- 系统事务信息
- 二次写缓冲(Double write buffer)
- per_table
- 数据
- 索引
- 插入缓冲bitmap页
- 共享表空间
段(segment)
table space由段组成
Innodb存储引擎是index organized,因此索引和数据是一起
常见的段有
- 数据段
B+树的叶子节点
-
索引段
B+树的非索引节点
-
回滚段
区(extent)
MySQL创建表空间的时候,不先申请一整个区,而是先申请一些碎片页(33个)。如果表的空间持续增加,用完所有碎片页以后,才会去申请区。
每个区大小都为1MB
为了保证页的连续性,Innodb每次从磁盘申请4~5个区
页(page)
Innodb中常见的页类型:
- 数据页(B-tree node)
- unod 页(undo log page)
- 系统页(system page)
- 事务数据页(transaction system page)
- 插入缓冲位图页(insert buffer bitmap)
- 插入缓冲空闲列表页(insert buffer free list)
- 未压缩的二进制对象页(uncompressed blob page)
- 压缩的二进制对象页(compressed blob page)
行(row)
InnoDB 是MySQL 的一种面向行(row-oriented)存储引擎
行记录格式
barracuda_file_format #新的文件格式。它支持InnoDB的所有行格式,包括新的行格式:COMPRESSED 和 DYNAMIC
├── antelope_file_format#它最开始并没有名字;Antelope 的名字是在新的文件格式 Barracuda 出现后才起的。它支持两种行格式:COMPACT 和 REDUNDANT
│ ├── compact# mysql5.0引入。一个页中存放的行数据越多
│ └── redundant#
├── compressed
└── dynamic
compact格式
variable length header | null Indicator | recorD header | row ID | transaction id | roll pointer | column1 | column2 |
---|---|---|---|---|---|---|---|
变长字段长度列表(2字节) | 该行数据是否有NULL值(1字节) | 记录头信息(5字节) | 【隐藏列】6字节 | 事务Id【隐藏列】6字节 | 回滚指针,指向这行数据的上一个版本。【隐藏列】7字节 | col数据1 | col数据2 |
记录头信息(5字节,40bit)
redundant格式
length offset list | recorD header | row ID | transaction id | roll pointer | column1 | column2 | column3 |
---|---|---|---|---|---|---|---|
字段长度偏移列表(1 or 2字节) | 记录头信息(6字节) | 【隐藏列】6字节 | 【隐藏列】7字节 | col数据1 | col数据2 | col数据3 |
create table z(
a int not null,
b int null,
c int not null,
d int not null,
unique key(b),
unique key(d),
unique key(c),
)
insert into z select 1,2,3,4;
insert into z select 5,6,7,8;
insert into z select 9,10,11,12;
select a,b,c,d,_rowid FROM z;
+---+------+----+----+--------+
| a | b | c | d | _rowid |
+---+------+----+----+--------+
| 1 | 2 | 3 | 4 | 4 |
| 5 | 6 | 7 | 8 | 8 |
| 9 | 10 | 11 | 12 | 12 |
+---+------+----+----+--------+
3 rows in set (0.00 sec)
建表时,如果没有显示得定义主键,innoDB将选择建表时第一个定义的非空唯一索引为主键。如果不符合上述条件,Innodb自动创建一个6直接指针指向它。
- 建表时,没有显示得定义主键
- 非空唯一索引
- 索引定义顺序
行溢出数据
mysql中磁盘与内存交互的基本单位是页,一般为16KB,16384
个字节,而一行记录最大可以占用65535
个字节,这就造成了一页存不下一行数据的情况
当 使用 Compact 或者 Redundant 格式存储极长的 VARCHAR 或者 BLOB 这类大对象
- 不会直接将所有的内容都存放在数据页节点
- 将行数据中的前 768 个字节存储在数据页中
- 后面会通过偏移量指向溢出页
当使用新的行记录格式 Compressed 或者 Dynamic 时
- 都只会在行记录中保存 20 个字节的指针
- 实际的数据都会存放在溢出页面中。
MySQL中规定一个页中至少存放两行记录
* 每个页除了存放我们的记录以外,也需要存储一些额外的信息,大概132个字节。
* 每个记录需要的额外信息是27字节。
假设一个列中存储的数据字节数为n,如要要保证该列不发生溢出,则需要满足:
132 + 2×(27 + n) < 16384
char 的行结构存储
使用的字符集不同,char类型列本部存储的可能不是定长的数据。
utf8 是 Mysql 中的一种字符集,只支持最长三个字节的 UTF-8字符,也就是 Unicode 中的基本多文本平面。
MySQL在5.5.3之后增加了这个utf8mb4的编码,mb4就是most bytes 4的意思,专门用来兼容四字节的unicode。
- latin1 单字节
- utf-8 char(10) 最少可以存储10字节的字符,最大可以存储30字节的字符。
- MySQL LENGTH(str) 函数的返回值为字符串的字节长度,使用 uft8(UNICODE 的一种变长字符编码,又称万国码)编码字符集时,一个汉字是 3 个字节,一个数字或字母是一个字节
- MySQL CHARACTER_LENGTH() returns the length of a given string. The length is measured in characters.
MariaDB [test]> create database hui_test;
Query OK, 1 row affected (0.027 sec)
MariaDB [test]> use hui_test;
Database changed
MariaDB [hui_test]> create table test_char(
-> c1 char(4)
-> )charset='gbk';
Query OK, 0 rows affected (0.354 sec)
MariaDB [hui_test]> insert into test_char values('我们');
Query OK, 1 row affected (0.039 sec)
MariaDB [hui_test]> insert into test_char values('abc');
Query OK, 1 row affected (0.002 sec)
MariaDB [hui_test]> select length(c1),character_length(c1),c1 from test_char;
+------------+----------------------+--------+
| length(c1) | character_length(c1) | c1 |
+------------+----------------------+--------+
| 4 | 2 | 我们 |
| 3 | 3 | abc |
+------------+----------------------+--------+
2 rows in set (0.036 sec)
数据页结构
一个 Innodb 页有以下七个部分:
fil header | page header | infimum superemum | user records | free space | page directory | file trailer |
---|---|---|---|---|---|---|
文件头(38字节) | 页头(56字节) | 字符串形式的“ Infimum”代表开头,“Supremum”表示结尾 | 与infimum列一起即行记录 | 空闲空间 | 页目录 | 文件结尾信息(8字节) |
user records, free space, page directory为实际的行记录存储空间,因此大小是动态的
fil header
Name | Size | Remarks |
---|---|---|
FIL_PAGE_SPACE |
4 | 4 ID of the space the page is in。(同一个文件可能有不同的页属于不同的表空间) |
FIL_PAGE_OFFSET |
4 | ordinal page number from start of space 表空间中页的偏移值 |
FIL_PAGE_PREV |
4 | offset of previous page in key order当前页的上一页 |
FIL_PAGE_NEXT |
4 | offset of next page in key order当前页的下一页 |
FIL_PAGE_LSN |
8 | log serial number of page’s latest log record 代表改页最后被修改的日志序列位置LSN。 |
FIL_PAGE_TYPE |
2 | 页的类型 |
FIL_PAGE_FILE_FLUSH_LSN |
8 | “the file has been flushed to disk at least up to this lsn” (log serial number), valid only on the first page of the file |
FIL_PAGE_ARCH_LOG_NO |
4 | the latest archived log file number at the time that FIL_PAGE_FILE_FLUSH_LSN was written (in the log) |
Page Header
The Page Header has 14 parts, as follows:
Name | Size | Remarks |
---|---|---|
PAGE_N_DIR_SLOTS |
2 | number of directory slots(槽) in the Page Directory part; initial value = 2 |
PAGE_HEAP_TOP |
2 | record pointer to first record in heap。(指向堆的第一个记录的指针,记录在页中是以堆的形式存放的) |
PAGE_N_HEAP |
2 | number of heap records; initial value = 2(堆中的记录数) |
PAGE_FREE |
2 | record pointer to first free record |
PAGE_GARBAGE |
2 | “number of bytes in deleted records”(即行记录结构中delete flag为1的记录大小的总数) |
PAGE_LAST_INSERT |
2 | record pointer to the last inserted record(最后插入记录的位置) |
PAGE_DIRECTION |
2 | either PAGE_LEFT , PAGE_RIGHT , or PAGE_NO_DIRECTION (最后插入的方向) |
PAGE_N_DIRECTION |
2 | number of consecutive inserts in the same direction, for example, “last 5 were all to the left”(一个方向连续插入记录的数量) |
PAGE_N_RECS |
2 | number of user records(该页中记录的数量) |
PAGE_MAX_TRX_ID |
8 | the highest ID of a transaction which might have changed a record on the page (only set for secondary indexes)(修改当前页的最大事务ID) |
PAGE_LEVEL |
2 | level within the index (0 for a leaf page) (页在当前索引页的位置。0x00代表叶节点) |
PAGE_INDEX_ID |
8 | identifier of the index the page belongs to(当前页属于哪个索引) |
PAGE_BTR_SEG_LEAF |
10 | “file segment header for the leaf pages in a B-tree” (this is irrelevant here) |
PAGE_BTR_SEG_TOP |
10 | “file segment header for the non-leaf pages in a B-tree” (this is irrelevant here) |
infimum and superemum
每个页有两个虚拟的行记录,用于限定记录的边界。infimum记录是比该页中所有主键值都要小的值,superemum记录是比该页中所有主键值都要大的值
Free space
链表结构,在一条记录被删除后,该空间会被加入到空闲链表中。
Page Directory
Page Directory(页目录)中存放了记录的相对位置(页相对位置,而不是偏移量),有时候这些记录指针成为Slots(槽)或者目录槽(Directory Slots)。【感觉类似于linux逻辑地址和物理地址使用】
- B+树索引本身并不能找到具体的一条记录,能找到只是该记录所在的页。
-
数据库把页载入到内存,然后通过page directory在进行二叉查找
- 由于innodb中Page Direcotry是稀疏目录,二叉查找只是个粗略的结果,因此Innodb存储引擎必须通过
recorder header
中的next_record
来继续查找相关记录。
FIle Trailer
为了检测页是否已经完整写入磁盘
- 前4字节代表该页的checksum
- 后4字节和File Header中的FIL_PAGE_LSN相同
mysql5.6.6版本开始新增参数innodb_checksum_algorithm
,该参数用来控制检测checksum函数的算法,默认为crc32。