InnoDB 的磁盘存储结构

表空间

是一个抽象的概念,对于系统表空间来说,对应着文件系统中一个或 多个实际文件,一般是(ibdata1);对于每个独立表空间(也就是上图的 File-Per-Table Tablespaces)来说,对应着文件系统中一个名为表名.ibd 的实际文 件。

可以把表空间想象成被切分为许许多多个页的池子,当我们想为某个表 插入一条记录的时候,就从池子中捞出一个对应的页来把数据写进去。 InnoDB 是以页为单位管理存储空间的,我们的聚簇索引(也就 是完整的表数据)和其他的二级索引都是以 B+树的形式保存到表空间的,而 B+ 树的节点就是数据页。 任何类型的页都有专门的地方保存页属于哪个表空间,同时表空间中的每一 个页都对应着一个页号,这个页号由 4 个字节组成,也就是 32 个比特位,所以 一个表空间最多可以拥有 2的32 次幂个页,如果按照页的默认大小 16KB 来算,一个表 空间最多支持 64TB 的数据。
独立表空间结构
区( extent )
表空间中的页可以达到 2的32 次幂 个页,实在是太多了,为了更好的管理这些页面, InnoDB 中还有一个区(英文名:extent)的概念。对于 16KB 的页来说,连续的 64 个页就是一个区,也就是说一个区默认占用 1MB 空间大小。 

不论是系统表空间还是独立表空间,都可以看成是由若干个区组成的,每 256 个区又被划分成一个组。

第一个组最开始的 3 个页面的类型是固定的:用来登记整个表空间的一些整 体属性以及本组所有的区被称为 FSP_HDR,也就是 extent 0 ~ extent 255 这 256 个区,整个表空间只有一个 FSP_HDR。

其余各组最开始的 2 个页面的类型是固定的,一个 XDES 类型,用来登记本组 256 个区的属性,FSP_HDR 类型的页面其实和 XDES 类型的页面的作用类似,只 不过 FSP_HDR 类型的页面还会额外存储一些表空间的属性。

引入区的主要目的是什么?

我们每向表中插入一条记录,本质上就是向该表 的聚簇索引以及所有二级索引代表的 B+树的节点中插入数据。而 B+树的每一层 中的页都会形成一个双向链表,如果是以页为单位来分配存储空间的话,双向链 表相邻的两个页之间的物理位置可能离得非常远。

B+树索引的适用场景特别提到范围查询只需要定位到最左边 的记录和最右边的记录,然后沿着双向链表一直扫描就可以了,而如果链表中相 邻的两个页物理位置离得非常远就是所谓的随机 I/O。再一次强调,磁盘的速 度和内存的速度差了好几个数量级,随机 I/O 是非常慢的,所以我们应该尽量让 链表中相邻的页的物理位置也相邻这样进行范围查询的时候才可以使用所谓的 顺序 I/O。 一个区就是在物理位置上连续的 64 个页。在表中数据量大的时候,为某个索 引分配空间的时候就不再按照页为单位分配了,而是按照区为单位分配,甚至在 表中的数据十分非常特别多的时候,可以一次性分配多个连续的区,从性能角度 看,可以消除很多的随机 I/O

我们提到的范围查询,其实是对 B+树叶子节点中的记录进行顺序扫描,而如 果不区分叶子节点和非叶子节点,统统把节点代表的页面放到申请到的区中的话, 进行范围扫描的效果就大打折扣了。所以 InnoDB 对 B+树的叶子节点和非叶子节 点进行了区别对待,也就是说叶子节点有自己独有的区,非叶子节点也有自己独 有的区。存放叶子节点的区的集合就算是一个段(segment),存放非叶子节点 的区的集合也算是一个段。也就是说一个索引会生成 2 个段,一个叶子节点段, 一个非叶子节点段。 段其实不对应表空间中某一个连续的物理区域,而是一个逻辑上的概念

系统表空间
系统表空间的结构和独立表空间基本类似,只不过由于整个 MySQL 进程只有 一个系统表空间,在系统表空间中会额外记录一些有关整个系统信息的页面,所 以会比独立表空间多出一些记录这些信息的页面,相当于是表空间之首,所以它 的表空间 ID(Space ID)是 0。 系统表空间有 extent 1 和 extent 两个区,也就是页号从 64~191 这 128 个页面 被称为 Doublewrite buffer,也就是双写缓冲区

双写缓冲区/双写机制

双写缓冲区/双写机制是 InnoDB 的三大特性之一,还有两个是 Buffer Pool、自 适应 Hash 索引。 它是一种特殊文件 flush 技术,带给 InnoDB 存储引擎的是数据页的可靠性。

它的作用是,在把页写到数据文件之前,InnoDB 先把它们写到一个叫 doublewrite buffer(双写缓冲区)的连续区域内,在写 doublewrite buffer 完成后,InnoDB 才 会把页写到数据文件的适当的位置。如果在写页的过程中发生意外崩溃,InnoDB 在稍后的恢复过程中在 doublewrite buffer 中找到完好的 page 副本用于恢复。 所以,虽然叫双写缓冲区,但是这个缓冲区不仅在内存中有,更多的是属于 MySQL 的系统表空间,属于磁盘文件的一部分。

那为什么要引入一个双写机制 呢?

InnoDB 的页大小一般是 16KB,其数据校验也是针对这 16KB 来计算的,将数 据写入到磁盘是以页为单位进行操作的。而操作系统写文件是以 4KB 作为单位的, 那么每写一个 InnoDB 的页到磁盘上,操作系统需要写 4 个块。 

而计算机硬件和操作系统,在极端情况下(比如断电)往往并不能保证这一 操作的原子性,16K 的数据,写入 4K 时,发生了系统断电或系统崩溃,只有一 部分写是成功的,这种情况下会产生 partial page write(部分页写入)问题。这 时页数据出现不一样的情形,从而形成一个"断裂"的页,使数据产生混乱。在 InnoDB 存储引擎未使用 doublewrite 技术前,曾经出现过因为部分写失效而导致 数据丢失的情况。

doublewrite buffer 是 InnoDB 在表空间上的 128 个页(2 个区,extend1 和 extend2),大小是 2MB。为了解决部分页写入问题,当 MySQL 将脏数据 flush 到数据文件的时候, 先使用 memcopy 将脏数据复制到内存中的一个区域(也是 2M),之后通过这个内存区域再分 2 次,每次写入 1MB 到系统表空间,然后马 上调用 fsync 函数,同步到磁盘(doublewrite)上。在这个过程中是顺序写,开销并不大,在完 成 doublewrite 写入后,再将数据写入各数据文件文件,这时是离散写入。 所以在正常的情况下, MySQL 写数据页时,会写两遍到磁盘上,第一遍是写到 doublewrite buffer,第二遍是写到真正的数据文件中。如果发生了极端情况(断 电),InnoDB 再次启动后,发现了一个页数据已经损坏,那么此时就可以从 doublewrite buffer 中进行数据恢复了。

位于系统表空间上的 doublewrite buffer 实际上也是一个文件,写 系统表空间会导致系统有更多的 fsync 操作, 而硬盘的 fsync 性能因素会降低 MySQL 的整体性能。不过在存储上,doublewrite 是在一个连续的存储空间, 所以 硬盘在写数据的时候是顺序写,而不是随机写,这样性能影响不大,相比不双写, 降低了大概 5-10%左右。

发生写失效时可以通过重做日志(Redo Log)进 行恢复,但是要注意,重做日志中记录的是对页的物理操作,如偏移量 800, 写' AA'记录,而不是页面的全量记录,而如果发生 partial page write(部分页写 入)问题时,出现问题的是未修改过的数据,此时重做日志(Redo Log)无能为力。 写 doublewrite buffer 成功了,这个问题就不用担心了。

如果是写 doublewrite buffer 本身失败,那么这些数据不会被写到磁盘,InnoDB 此时会从磁盘载入原始的数据,然后通过 InnoDB 的事务日志来计算出正确的数 据,重新写入到 doublewrite buffer,这个速度就比较慢了。如果 doublewrite buffer 写成功的话,但是写数据文件失败,innodb 就不用通过事务日志来计算了,而是直 接用 doublewrite buffer 的数据再写一遍,速度上会快很多。 总体来说,doublewrite buffer 的作用有两个: 提高 innodb 把缓存的数据写到 硬盘这个过程的安全性;间接的好处就是,innodb 的事务日志不需要包含所有 数据的前后映像,而是二进制变化量,这可以节省大量的 IO。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值