网校研发部--施洪宝
一. 简介
- Mysql是目前最为流行的关系型数据库管理系统, 具有体积小、速度快、开放源码等优势。InnoDB是Mysql使用最广泛的存储引擎, InnoDB进行了行锁设计, 支持MVCC, 提供一致性非锁定读。学习InnoDB数据页存储, 能够让我们更加深入的理解InnoDB的一些特性。
- 程序 = 数据结构 + 算法, 对于Mysql而言也是如此。由于数据持久化的需要, Mysql的数据不仅存储在内存中, 也会持久化到文件中, 存储结构如下图,
- 从磁盘中, 我们可以很容易的看到持久化的各个文件。
- 磁盘中的文件需要加载到内存中才能被程序使用, 很明显, 不可能将所有磁盘文件都加载到内存, 当内存中的数据发生更改后, 也需要刷新到磁盘文件中, 什么时候刷新, 怎么刷新, 这些都是Mysql需要考虑的问题, 但是这些内容不是本文的重点, 我们这里稍加了解即可。
- 本文的重点是学习数据页的存储, 这些数据页可能存在与系统表空间, 独立表空间或者临时表空间。可以看到, 这些只是图中的一小部分。
3.学习之前, 我们先考虑几个问题,
- 无论是内存存储还是磁盘存储, 都离不开内存管理, InnoDB是如何划分内存以及如何管理内存的?
- InnoDB使用B+树存储我们表中的数据, B+树索引节点以及叶子节点应该需要存储哪些数据? 又是怎么存储的?
- 我们在使用时, 创建了数据库, 数据表, 这些元数据是如何存储的, 查询某个表时, 如何根据元数据找到表的索引, 如何选择索引, 选择索引后, 如何定位到索引的根节点(root page)? 找到跟节点后, 又是如何一步步找到某个具体数据的?
4.说明
- Mysql版本: 8.0.12-debug
- 存储引擎使用InnoDB
- 我们会用到xxd命令, 使用xxd(或者hexdump)可以以十六进制的方式查看文件。
二. InnoDB存储结构
InnoDB存储结构图如下所示, 我们这里只做简要的介绍, 更多细节我们将在后续的文章中再进行详细阐述,
- 表空间(tablespace)可以认为是InnoDB存储引擎存储结构的最高层, 所有数据都在表空间中, 除了共享表空间外, 每个表可以创建独立表空间, 具体参数是由innodb_file_per_table参数决定, 表空间由各种段组成。
- 常见的段(segment)有数据段, 回滚段, 索引段。innodb中数据段就是B+树的叶子节点, 索引段就是B+树中的非叶子节点。
- 段是由区(extent)组成, 默认情况下区的大小是1MB, InnoDB默认页大小为16KB, 所以1个区是由16个连续页组成。
- innodb默认页(page)大小是16KB, 也可以通过innodb_page_size进行控制。
- innodb存储是面向行(row)的, 行的存储格式主要有compact、redundant、compressed、dynamic。
三. 数据页存储
3.1 独立表空间
通过innodb_file_per_table参数, 我们可以为每个表都创建一个表空间, 这个就是这个表的独立表空间, 这个表的索引段, 数据段都会存储在这个独立表空间中, 但是Redo log, Undo log仍然在各自的表空间中, 表空间存储如下图,
- 表空间的page 0是表空间的第一页, 存储了表空间的信息, 同时也用于管理前256个extent。page 16384类型为FIL_PAGE_TYPE_XDES也用于管理之后的256个extent, 以此类推, 每隔16384个页面都会需要一个FIL_PAGE_TYPE_XDES页面。
- page 1类型是FIL_PAGE_IBUF_BITMAP, 用于管理每个page(前256个extent的16384个页面)的change buffer(change buffer相关内容不是本文的重点, 感兴趣的读者可以查找相关资料)。与FIL_PAGE_TYPE_XDES类似, 每隔16384个页面都需要一个FIL_PAGE_IBUF_BITMAP页面。
- page 2类型为FIL_PAGE_INODE, 用于管理segment。
- page 3类型为FIL_PAGE_SDI, 存储Serialized Dictionary Information(SDI, 词典序列化信息), 存储了这个表空间的一些数据字典(Data Dictionary)信息。
- page 4一般就是这个表主键索引的root page。
3.2 页存储
InnoDB的页存储结构如下, 每页都是由3部分组成, File Header(38字节)、File Body、File Trailer(8字节), 不同页的File Body存储的内容不同,
1.File Header
名称 | 大小 | 说明 |
---|---|---|
FIL_PAGE_SPACE_OR_CHKSUM | 4字节 | 页的校验码 |
FIL_PAGE_OFFSET | 4字节 | 表空间中页的便宜量 |
FIL_PAGE_PREV | 4字节 | 上一页 |
FIL_PAGE_NEXT | 4字节 | 下一页 |
FIL_PAGE_LSN | 8字节 | 页面被最后修改时对应的日志序列位置 |
FIL_PAGE_TYPE | 2字节 | 页面类型 |
FIL_PAGE_FILE_FLUSH_LSN | 8字节 | 系统表空间中有定义, 代表文件更新到的LSN |
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID | 4字节 | 页面所属表空间id |
2.File Type
名称 | 值 | 说明 |
---|---|---|
FIL_PAGE_TYPE_ALLOCATED | 0x0000 | 未使用 |
FIL_PAGE_UNDO_LOG | 0x0002 | undo log |
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_SDI | 0x45bd | SDI索引页 |
FIL_PAGE_RTREE | 0x45be | R-tree |
FIL_PAGE_INDEX | 0x45bf | B-tree |
3.3 数据页
看完InnoDB页结构后, 我们看下数据页的存储,
1.Page Header
名称 | 大小 | 说明 |
---|---|---|
PAGE_N_DIR_SLOTS | 2字节 | page directory中slot的个数 |
PAGE_HEAP_TOP | 2字节 | 堆中第一个记录指针 |
PAGE_N_HEAP | 2字节 | 堆中记录数 |
PAGE_FREE | 2字节 | 指向空闲空间首地址 |
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字节 | 当前页在索引中的层, 叶子节点为0x00 |
PAGE_INDEX_ID | 8字节 | 索引ID |
PAGE_BTR_SEG_LEAF | 10字节 | 非叶子节点所在段, 仅在B+树的root页中有定义 |
PAGE_BTR_SEG_TOP | 10字节 | 数据页所在段, 仅在B+树的root页中有定义 |
2.Infimun & Supermum
虚拟记录, Infimum为13字节, Supermum也是13字节。具体存储内容, 我们会在下面进行介绍。
3.Page Directory
- 页目录, 因为行记录在数据页中以链表的形式链接, 但是在查找记录时, 链表查找速度很慢, 为了加速记录查找, 创建页目录, 页目录可以用于二分查找。每个目录项占用2个字节, 从页尾部开始, 倒序存储。
- 为了便于理解Page Directory, 我们这里举一个例子, 如果表中存储了200条数据, 数据通过链表的方式进行链接, 我们在查询时, 需要遍历整个链表才能找到数据, 这样无疑比较慢。我们可以通过建立索引的方式, 加快查找速度, 我们可以将这200条记录的主键按照顺序进行存储, InnoDB的Page Directory就是这个思路, 但是并不是存储了主键的值, 而是存储了对应记录的位置, 并且不是将每个行记录都存储在Page Directory中, 只是建立一个稀疏索引。
3.4 innodb行存储
限于篇幅, 我们这里主要介绍compact格式的行记录存储, 存储格式如下图,
- 从图中可以看出, 每个记录行至少占有5字节(记录头) + 主键长度 + 6字节(事务ID) + 7字节(回滚指针)
- 我们需要注意记录头中的next_record字段, 这个字段占有16bit, 也就是2个字节, 通过这个字段, InnoDB将一个页中的所有记录以链表的方式链接到一起。
四. 实例讲解
为了便于大家理解, 这部分我们给出一些实例,
- 本节举例说明InnoDB的一个表是如何存储的, 主要介绍两种情况, 一种情况是表中数据很少, 另一种情况是表中数据比较多, 一页已经存储不了的情况。
- 表结构定义,
create table `t` (`id` int not null, primary key(`id`)) engine=InnoDB ROW_FORMAT=Compact;
- 为了更容易理解, 我们这里只创建了一个非常简单的表, 也只有一个主键索引。主键类型为int, 占用4个字节。
- 创建表后, 可以在相应的目录下看到t.ibd文件, 这里我是在test数据库下创建的这个表, 所以也就在test目录下。
- 从磁盘文件中, 我们可以看到, t.ibd文件大小为112KB, 也就是7*16KB, 也就是7个page, 也就意味着, 创建表后, InnoDB默认初始化了7个page。
- 我们的表中没有变长字段, 主键长度为4字节, 所以单个记录的长度为5(记录头) + 4(主键ID) + 6(事务ID) + 7(回滚指针) = 22字节
3.B+树示例
InnoDB数据存储是通过B+树组织的, 一个很简单的B+树如下所示,
- B+树的性质有很多, 其增删查改操作较常规的二叉树更复杂一些, 感兴趣的可以查询相关资料, 这里有个基本概念即可。
4.后续如果没有特殊说明, 表空间第一个页是page 0, 第二页是page 1, 以此类推。
4.1 单页存储
我们首先看下当表中数据很少的时候, 数据是如何组织的, 具体操作步骤如下,
1.我们向表中插入2条记录,
insert into t values (2);
insert into t values (1);
- 这里注意我们先插入主键值为2的行记录, 再插入主键值为1的行记录。
2.通过xxd将t.ibd以16进制表示, 执行命令xxd t.idb t.txt, 也可以使用hexdump命令查看。
3.查看t.txt中的内容, 这里我们查看page 4的数据
0010000: a76e 6043 0000 0004 ffff ffff ffff ffff .n`C............
0010010: 0000 0000 012e 6d9d 45bf 0000 0000 0000 ......m.E.......
0010020: 0000 0000 0005 0002 00a4 8004 0000 0000 ................
0010030: 0093 0001 0001 0002 0000 0000 0000 0000 ................
0010040: 0000 0000 0000 0000 0091 0000 0005 0000 ................
0010050: 0002 0272 0000 0005 0000 0002 01b2 0100 ...r............
0010060: 0200 3069 6e66 696d 756d 0003 000b 0000 ..0infimum......
0010070: 7375 7072 656d 756d 0000 10ff f380 0000 supremum........
0010080: 0200 0000 001c 0481 0000 00fa 0110 0000 ................
0010090: 18ff ea80 0000 0100 0000 001c 0582 0000 ................
00100a0: 012c 0110 0000 0000 0000 0000 0000 0000 .,..............
00100b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
......
......
0013fe0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0013ff0: 0000 0000 0070 0063 a76e 6043 012e 6d9d .....p.c.n`C..m.
- 前38字节是文件头[0010000,0010026]
- 之后56字节是数据页头部[0010027,001005d]
- 之后的26字节是最小记录[001005e, 001006a], 最大记录[001006b, 0010077], 这里可以看到最小记录的n_owns值为1(只有自身1条记录), 最大记录的n_owns值为3(除了自身外, 还有我们插入的两条记录)
- 紧接着是第1条插入记录[0010078, 001008d]
0010070: .... .... .... .... 0000 10ff f380 0000
0010080: 0200 0000 001c 0481 0000 00fa 0110 ....
- 最后是刚才插入的第2条记录[001008e, 00100a3]
- 对于int类型, innodb存储方式与常规的方式不同, [0x00000000, 0x7fffffff]代表[-2147483648, -1], [0x80000000, 0xffffffff]代表[0, 21473647]。
4.存储结构如下图
5.这里示例下如何从最小记录查找到最大记录
- 首先定位到最小记录的位置, 最小记录占有5字节(记录头) + 8字节(内容) = 13字节, 最小记录所在的位置为0010063, 根据最小记录的记录头信息, 可以计算出下一个记录所在位置0010063 + 0030 = 0010093。这里需要注意的是, 记录所在位置使用的是内容开始的位置。
- 0010093是主键为1的记录所在位置, 接着计算下一个记录的位置0010093 + ffea = 001007d, 这里需要注意的是, 加法运算时, 只保留后面4位的结果, 可以看到这个位置就是我们第一次插入的主键为2的记录
- 之后, 继续计算下一个记录所在位置, 001007d + fff3 = 10070, 这个就是最大记录所在的位置
- 在查找某个具体的行记录时, 可以先利用page directory进行近似的二分查找, 之后再进行链表查找。
6.page directory
- 页尾部包含两个slots
0013ff0: 0000 0000 0070 0063 .... .... .... ....
- 0063是第1个slot的位置, 相应的记录所在位置为0010063, 也就是最小记录。[001005e, 0010062]这个是最小记录的记录头, [0010063, 001006a]是最小记录的内容。
- 0070是第2个slot的位置, 相应的记录所在位置为0010070, 这个是最大记录所在的内容开始位置。
7.小结
- 可以看到, 从最小记录开始, 到最大记录结束, 数据按照主键顺序以链表的方式进行链接。
- 行数据的存储是按照插入的顺序存储的, 不是按照主键顺序存储, 数据删除后, 释放的空间可以复用, 关于复用部分的细节, 后续文章再进行详细介绍。
4.2 多页存储
在4.1的基础上, 我们继续插入数据, 操作步骤如下,
- 我们通过脚本向表中继续插入数据
<?php
$servername = "localhost:8083";
$username = "root";
$password = "password";
$dbname = "test";
try {
$conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
for($i = 3; $i < 1000; $i++){
$sql = "INSERT INTO t VALUES (" . strval($i) . ")";
$conn->exec($sql);
}
}
catch(PDOException $e){
echo $sql . "<br>" . $e->getMessage();
}
$conn = null;
?>
- 之前表中已经插入2条记录, 这里又插入997条记录, 所以表中现在一共999条记录, 主键id从1到999。
- 单条记录需要占用22字节, 可以知道, 此时, 单个数据页不能存储全部数据。
2.以16进制查看此时的t.ibd文件: xxd t.ibd t.txt
3.查看t.txt内容, 首先查看page 4的内容
0010000: df67 193d 0000 0004 ffff ffff ffff ffff .g.=............
0010010: 0000 0000 0132 5500 45bf 0000 0000 0000 .....2U.E.......
0010020: 0000 0000 0005 0002 0092 8004 0000 0000 ................
0010030: 008a 0002 0001 0002 0000 0000 0000 0000 ................
0010040: 0001 0000 0000 0000 0091 0000 0005 0000 ................
0010050: 0002 0272 0000 0005 0000 0002 01b2 0100 ...r............
0010060: 0200 1a69 6e66 696d 756d 0003 000b 0000 ...infimum......
0010070: 7375 7072 656d 756d 1000 1100 0d80 0000 supremum........
0010080: 0100 0000 0500 0019 ffe6 8000 0153 0000 .............S..
0010090: 0006 0000 0000 0000 0000 0000 0000 0000 ................
.......
.......
0013fe0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0013ff0: 0000 0000 0070 0063 df67 193d 0132 5500 .....p.c.g.=.2U.
- 可以看到, 第5页, 目前只有少量内容, 因为此时第5页是索引页, 是B+树的根, 没有存储具体的数据, 只存储了主键索引。
- File Header, Page Header, Infimum & Supremum跟之前基本类似, 这里就不再详细介绍。
- 单个索引需要占用5字节(记录头) + 4字节(主键) + 4字节(记录所在页) = 13字节。
- 第1个索引信息
0010070: .... .... .... .... 1000 1100 0d80 0000
0010080: 0100 0000 05.. .... .... .... .... ....
主键id为0x80000001, 也就是1, page no为0x00000005, 也就是page 5
- 第2个索引信息
0010080: .... .... ..00 0019 ffe6 8000 0153 0000
0010090: 0006 .... .... .... .... .... .... ....
主键id为0x80000153, 也就是339, page no为0x00000006, 也就是page 6
- 通过这两个索引信息, 可以知道, page 5存储着主键id从1到338的数据, page 6存储着主键id从339到999的数据
4.查看page 5
0014000: e1c0 bb7a 0000 0005 ffff ffff 0000 0006 ...z............
0014010: 0000 0000 0132 5500 45bf 0000 0000 0000 .....2U.E.......
0014020: 0000 0000 0005 0056 3a90 82a6 1d89 1d0c .......V:.......
0014030: 0000 0005 0000 0152 0000 0000 0000 0000 .......R........
0014040: 0000 0000 0000 0000 0091 0000 0000 0000 ................
0014050: 0000 0000 0000 0000 0000 0000 0000 0100 ................
0014060: 0200 1a69 6e66 696d 756d 0003 000b 0000 ...infimum......
0014070: 7375 7072 656d 756d 0000 1000 1680 0000 supremum........
0014080: 0100 0000 001c 0582 0000 012c 0110 0000 ...........,....
0014090: 1800 1680 0000 0200 0000 001c 0481 0000 ................
00140a0: 00fa 0110 0000 2000 1680 0000 0300 0000 ...... .........
.......
.......
0017e90: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0017ea0: 0000 0000 0070 3a27 39cf 3977 391f 38c7 .....p:'9.9w9.8.
0017eb0: 386f 3817 37bf 3767 370f 36b7 365f 3607 8o8.7.7g7.6.6_6.
0017ec0: 35af 3557 34ff 34a7 344f 33f7 339f 3347 5.5W4.4.4O3.3.3G
0017ed0: 32ef 3297 323f 31e7 318f 3137 30df 3087 2.2.2?1.1.170.0.
0017ee0: 302f 2fd7 2f7f 2f27 2ecf 2e77 2e1f 2dc7 0//././'...w..-.
0017ef0: 2d6f 2d17 2cbf 2c67 2c0f 2bb7 2b5f 2b07 -o-.,.,g,.+.+_+.
0017f00: 2aaf 2a57 29ff 29a7 294f 28f7 289f 2847 *.*W).).)O(.(.(G
0017f10: 27ef 2797 273f 26e7 268f 2637 25df 2587 '.'.'?&.&.&7%.%.
0017f20: 252f 24d7 247f 2427 23cf 2377 231f 22c7 %/$.$.$'#.#w#.".
0017f30: 226f 2217 21bf 2167 210f 20b7 205f 2007 "o".!.!g!. . _ .
0017f40: 1faf 1f57 1eff 1ea7 1e4f 1df7 0070 1d47 ...W.....O...p.G
0017f50: 1cef 1c97 1c3f 1be7 1b8f 1b37 1adf 1a87 .....?.....7....
0017f60: 1a2f 19d7 197f 1927 18cf 1877 181f 17c7 ./.....'...w....
0017f70: 176f 1717 16bf 1667 160f 15b7 155f 1507 .o.....g....._..
0017f80: 14af 1457 13ff 13a7 134f 12f7 129f 1247 ...W.....O.....G
0017f90: 11ef 1197 113f 10e7 108f 1037 0fdf 0f87 .....?.....7....
0017fa0: 0f2f 0ed7 0e7f 0e27 0dcf 0d77 0d1f 0cc7 ./.....'...w....
0017fb0: 0c6f 0c17 0bbf 0b67 0b0f 0ab7 0a5f 0a07 .o.....g....._..
0017fc0: 09af 0957 08ff 08a7 084f 07f7 079f 0747 ...W.....O.....G
0017fd0: 06ef 0697 063f 05e7 058f 0537 04df 0487 .....?.....7....
0017fe0: 042f 03d7 037f 0327 02cf 0277 021f 01c7 ./.....'...w....
0017ff0: 016f 0117 00bf 0063 e1c0 bb7a 0132 5500 .o.....c...z.2U.
- 注意页尾部包含page directory, slots的个数可以从page header中读取
- File Header中的FIL_PAGE_NEXT字段, 值为0x00000006, 也就是page no为6的页。
5.查看page 6
0018000: 2ddb 788c 0000 0006 0000 0005 ffff ffff -.x.............
0018010: 0000 0000 0133 f431 45bf 0000 0000 0000 .....3.1E.......
0018020: 0000 0000 0005 00a6 3946 8297 0000 0000 ........9F......
0018030: 3935 0002 0142 0295 0000 0000 0000 0000 95...B..........
0018040: 0000 0000 0000 0000 0091 0000 0000 0000 ................
0018050: 0000 0000 0000 0000 0000 0000 0000 0100 ................
0018060: 0200 1a69 6e66 696d 756d 0006 000b 0000 ...infimum......
0018070: 7375 7072 656d 756d 0000 1000 1680 0001 supremum........
0018080: 5300 0000 001d 6d81 0000 00a3 0110 0000 S.....m.........
.......
.......
001bea0: 0000 0000 0000 0000 0000 0000 0070 38c7 .............p8.
001beb0: 386f 3817 37bf 3767 370f 36b7 365f 3607 8o8.7.7g7.6.6_6.
001bec0: 35af 3557 34ff 34a7 344f 33f7 339f 3347 5.5W4.4.4O3.3.3G
001bed0: 32ef 3297 323f 31e7 318f 3137 30df 3087 2.2.2?1.1.170.0.
001bee0: 302f 2fd7 2f7f 2f27 2ecf 2e77 2e1f 2dc7 0//././'...w..-.
001bef0: 2d6f 2d17 2cbf 2c67 2c0f 2bb7 2b5f 2b07 -o-.,.,g,.+.+_+.
001bf00: 2aaf 2a57 29ff 29a7 294f 28f7 289f 2847 *.*W).).)O(.(.(G
001bf10: 27ef 2797 273f 26e7 268f 2637 25df 2587 '.'.'?&.&.&7%.%.
001bf20: 252f 24d7 247f 2427 23cf 2377 231f 22c7 %/$.$.$'#.#w#.".
001bf30: 226f 2217 21bf 2167 210f 20b7 205f 2007 "o".!.!g!. . _ .
001bf40: 1faf 1f57 1eff 1ea7 1e4f 1df7 1d9f 1d47 ...W.....O.....G
001bf50: 1cef 1c97 1c3f 1be7 1b8f 1b37 1adf 1a87 .....?.....7....
001bf60: 1a2f 19d7 197f 1927 18cf 1877 181f 17c7 ./.....'...w....
001bf70: 176f 1717 16bf 1667 160f 15b7 155f 1507 .o.....g....._..
001bf80: 14af 1457 13ff 13a7 134f 12f7 129f 1247 ...W.....O.....G
001bf90: 11ef 1197 113f 10e7 108f 1037 0fdf 0f87 .....?.....7....
001bfa0: 0f2f 0ed7 0e7f 0e27 0dcf 0d77 0d1f 0cc7 ./.....'...w....
001bfb0: 0c6f 0c17 0bbf 0b67 0b0f 0ab7 0a5f 0a07 .o.....g....._..
001bfc0: 09af 0957 08ff 08a7 084f 07f7 079f 0747 ...W.....O.....G
001bfd0: 06ef 0697 063f 05e7 058f 0537 04df 0487 .....?.....7....
001bfe0: 042f 03d7 037f 0327 02cf 0277 021f 01c7 ./.....'...w....
001bff0: 016f 0117 00bf 0063 2ddb 788c 0133 f431 .o.....c-.x..3.1
- 注意File Header中的FIL_PAGE_PREV字段, 值为0x00000005, 也就是page no为5的页。
- 结合page 5可以看出, 叶子节点的两个页通过链表进行链接, 每个页内的数据通过记录头中的next_record字段进行链接。
6.存储结构图如下,
7.小结
- 对于单页存储不了的情况, 需要进行页分裂, 此时B+树会有多层结构, 最低层为叶子节点, 存储了具体的数据, 上面是索引节点, 只存储主键以及下一层节点所在的页信息
五. 总结与思考
本文介绍了innodb的数据页存储, 以实例的方式讲解了innodb存储引擎如何存储一个表中数据的。但是我们仍然有很多问题没有给出答案,
- 查找行记录时, 需要找到某个索引的root page, 这个信息是存储在哪里的?
- 我们没有介绍段和区的相关内容, 这些在InnoDB数据存储时是如何使用的?
- 我们查看数据时, 都是直接查看磁盘文件, 内存中的页与磁盘中的页有何区别, 内存中的脏页又是如何刷新到磁盘的?
InnoDB存储引擎较为复杂, 不可能一次性将全部内容学会, 我们不妨每次带入一个问题, 深入寻找这个问题的答案, 关于这些问题, 我会在后续文章中再逐步介绍。
六. 参考
- <<Mysql技术内幕 InnoDB存储引擎>>
- 淘宝数据库内核月报
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。