任何一个技术都有其底层的关键基础技术, 这些关键技术很有可能也是其他技术的关键技术, 学习这些底层技术, 就可以一通百通, 让你很快的掌握其他技术. 如何在磁盘上存储数据, 如何使用日志文件保证数据不丢失以及如何落盘, 不仅是 MySQL 等数据库的关键技术, 也是 MQ 消息队列或者其他中间件的关键技术之一.
上图详细显示了 InnoDB 存储引擎的体系架构, 从图中可见, InnoDB 存储引擎由内存池, 后台线程和磁盘文件三大部分组成. 接下来我们就来简单了解一下磁盘文件相关的概念和原理.
InnoDB 的主要的磁盘文件主要分为三大块: 一是系统表空间, 二是用户表空间, 三是 redo 日志文件和归档文件. 二进制文件 (binlog) 等文件是 MySQL Server 层维护的文件, 所以未列入 InnoDB 的磁盘文件中.
系统表空间和用户表空间
InnoDB 系统表空间包含 InnoDB 数据字典 (元数据以及相关对象) 并且 doublewrite buffer,change buffer,undo logs 的存储区域. 系统表空间也默认包含任何用户在系统表空间创建的表数据和索引数据. 系统表空间是一个共享的表空间因为它是被多个表共享的
系统表空间是由一个或者多个数据文件组成. 默认情况下, 1 个初始大小为 10MB, 名为 ibdata1 的系统数据文件在 MySQL 的 data 目录下被创建. 用户可以使用 innodb_data_file_path 对数据文件的大小和数量进行配置.
innodb_data_file_path 的格式如下:
innodb_data_file_path=datafile1[,datafile2]...
复制代码
用户可以通过多个文件组成一个表空间, 同时制定文件的属性:
innodb_data_file_path = /db/ibdata1:1000M;/dr2/db/ibdata2:1000M:autoextend
复制代码
这里讲 / db/ibdata1 和 / dr2/db/ibdata2 两个文件组成系统表空间. 如果这两个文件位于不同的磁盘上, 磁盘的负载可能被平均, 因此可以提高数据库的整体性能. 两个文件的文件名之后都跟了属性, 表示文件 ibdata1 的大小为 1000MB, 文件 ibdata2 的大小为 1000MB, 而且用完空间之后可以自动增长(autoextend).
设置 innodb_data_file_path 参数之后, 所以基于 InnoDB 存储引擎的表的数据都会记录到该系统表空间中, 如果设置了参数 innodb_file_per_table, 则用户可以将每个基于 InnoDB 存储引擎的表产生一个独立的用户表空间. 用户表空间的命名规则为: 表名. ibd. 通过这种方式, 用户不用将所有数据都存放于默认的系统表空间中, 但是用户表空只存储该表的数据, 索引和插入缓冲 BITMAP 等信息, 其余信息还是存放在默认的表空间中.
上图显示 InnoDB 存储引擎对于文件的存储方式, 其中 frm 文件是表结构定义文件, 记录每个表的表结构定义.
重做日志文件和归档文件
默认情况下, 在 InnoDB 存储引擎的数据目录下会有两个名为 ib_logfile0 和 ib_logfile1 的文件, 这就是 InnoDB 的重做日志文件(redo log fiel), 它记录了对于 InnoDB 存储引擎的事务日志. 当 InnoDB 的数据存储文件发生错误时, 重做日志文件就能派上用场. InnoDB 存储引擎可以使用重做日志文件将数据恢复为正确状态, 以此来保证数据的正确性和完整性.
每个 InnoDB 存储引擎至少有 1 个重做日志文件组(group), 每个文件组下至少有 2 个重做日志文件, 如默认的 ib_logfile0 和 ib_logfile1. 为了得到更高的可靠性, 用户可以设置多个镜像日志组, 将不同的文件组放在不同的磁盘上, 以此来提高重做日志的高可用性.
在日志组中每个重做日志文件的大小一致, 并以循环写入的方式运行. InnoDB 存储引擎先写入重做日志文件 1, 当文件被写满时, 会切换到重做日志文件 2, 再当重做日志文件 2 也被写满时, 再切换到重做日志文件 1.
用户可以使用 innodb_log_file_size 来设置重做日志文件的大小, 这对 InnoDB 存储引擎的性能有着非常大的影响.
如果重做日志文件设置的太大, 数据丢失时, 恢复时可能需要很长的时间; 另一方面, 如果设置的太小, 重做日志文件太小会导致依据 checkpoint 的检查需要频繁刷新脏页到磁盘中, 导致性能的抖动. 重做日志相关和 Checkpoint 的机制可以阅读我之前文章的相应章节. MySQL 探秘(三):InnoDB 的内存结构和特性 https://mp.weixin.qq.com/s/JCXdP59OUvQEB3q1V9YrCA
重做日志的落盘机制
InnoDB 对于数据文件和日志文件的刷盘遵守 WAL(Write ahead redo log) 和 Force-log-at-commit 两种规则, 二者保证了事务的持久性. WAL 要求数据的变更写入到磁盘前, 首先必须将内存中的日志写入到磁盘; Force-log-at-commit 要求当一个事务提交时, 所有产生的日志都必须刷新到磁盘上, 如果日志刷新成功后, 缓冲池中的数据刷新到磁盘前数据库发生了宕机, 那么重启时, 数据库可以从日志中恢复数据.
如上图所示, InnoDB 在缓冲池中变更数据时, 会首先将相关变更写入重做日志缓冲中, 然后再按时或者当事务提交时写入磁盘, 这符合 Force-log-at-commit 原则; 当重做日志写入磁盘后, 缓冲池中的变更数据才会依据 checkpoint 机制择时写入到磁盘中, 这符合 WAL 原则. 在 checkpoint 择时机制中, 就有重做日志文件写满的判断, 所以, 如前文所述, 如果重做日志文件太小, 经常被写满, 就会频繁导致 checkpoint 将更改的数据写入磁盘, 导致性能抖动.
操作系统的文件系统是带有缓存的, 当 InnoDB 向磁盘写入数据时, 有可能只是写入到了文件系统的缓存中, 没有真正的 "落袋为安". InnoDB 的 innodb_flush_log_at_trx_commit 属性可以控制每次事务提交时 InnoDB 的行为. 当属性值为 0 时, 事务提交时, 不会对重做日志进行写入操作, 而是等待主线程按时写入; 当属性值为 1 时, 事务提交时, 会将重做日志写入文件系统缓存, 并且调用文件系统的 fsync, 将文件系统缓冲中的数据真正写入磁盘存储, 确保不会出现数据丢失; 当属性值为 2 时, 事务提交时, 也会将日志文件写入文件系统缓存, 但是不会调用 fsync, 而是让文件系统自己去判断何时将缓存写入磁盘. 日志的刷盘机制如下图所示.
innodb_flush_log_at_commit 是 InnoDB 性能调优的一个基础参数, 涉及 InnoDB 的写入效率和数据安全. 当参数值为 0 时, 写入效率最高, 但是数据安全最低; 参数值为 1 时, 写入效率最低, 但是数据安全最高; 参数值为 2 时, 二者都是中等水平. 一般建议将该属性值设置为 1, 以获得较高的数据安全性, 而且也只有设置为 1, 才能保证事务的持久性.
来源: https://juejin.im/post/5b94884af265da0ae74f60d3