文章目录
- [6. InnoDB 磁盘文件](#6. InnoDB 磁盘文件)
-
- [6.8 双写缓冲区 - Doublewrite Buffer](#6.8 双写缓冲区 - Doublewrite Buffer)
-
- [6.8.1 双写缓冲区的作用?](#6.8.1 双写缓冲区的作用?)
-
- [6.8.1.1 双写缓冲区中的数据保存在哪里?](#6.8.1.1 双写缓冲区中的数据保存在哪里?)
- [6.8.2 如何配置双写缓冲区?](#6.8.2 如何配置双写缓冲区?)
- [6.9 重做日志 - Redo Log](#6.9 重做日志 - Redo Log)
-
- [6.9.1 重做日志的作用?](#6.9.1 重做日志的作用?)
-
- [6.9.1.1 为什么要用Redo Log,而不是直接写磁盘?](#6.9.1.1 为什么要用Redo Log,而不是直接写磁盘?)
- [6.9.1.2 Redo Log的写入时机?](#6.9.1.2 Redo Log的写入时机?)
- [6.9.2 Redo Log的格式是怎样的?](#6.9.2 Redo Log的格式是怎样的?)
-
- [6.9.2.1 RedoLog中需要记录哪些内容?](#6.9.2.1 RedoLog中需要记录哪些内容?)
- [6.9.2.2 data 部分的具体内容是什么?](#6.9.2.2 data 部分的具体内容是什么?)
- [6.9.3 RedoLog的类型分为哪些?](#6.9.3 RedoLog的类型分为哪些?)
- [6.9.4 不同日志类型对应了哪些操作?](#6.9.4 不同日志类型对应了哪些操作?)
-
- [6.9.4.1 如果一个DML操作修改了表中的多个字段,日志如何表示?](#6.9.4.1 如果一个DML操作修改了表中的多个字段,日志如何表示?)
6. InnoDB 磁盘文件
6.8 双写缓冲区 - Doublewrite Buffer
6.8.1 双写缓冲区的作用?
双写缓冲区是磁盘上的一个存储区域,当
InnoDB将缓冲池中的数据页写入到磁盘上表空间数据文件之前,先将对应的页写到双写缓冲区;如果在数据真正落盘的过程中出现了意外退出,比如操作系统、存储子系统崩溃或异常断电的情况,InnoDB在崩溃恢复时可以从双写缓冲区中找到一份完好的页副本。执行过程如下图所示:

6.8.1.1 双写缓冲区中的数据保存在哪里?
在
MySQL 8.0.20之前,doublewrite缓冲区位于InnoDB系统表空间中。从MySQL 8.0.20开始,doublewrite缓冲区默认存储区域位于数据目录下的doublewrite文件中。
mysql
root@yudukai:/var/lib/mysql# ll
# ...省略
# 默认创建两个双写缓冲区文件
-rw-r----- 1 mysql mysql 196608 May 29 08:52 '#ib_16384_0.dblwr' # 文件1
-rw-r----- 1 mysql mysql 8585216 Oct 27 2025 '#ib_16384_1.dblwr' # 文件2
# ...省略
6.8.2 如何配置双写缓冲区?
- 是否启用
doublewrite缓冲区可以通过系统变量innodb_doublewrite[=ON|OFF]控制,默认为启用,如果真实的业务场景更关注性能而不是数据完整性,可以考虑禁用doublewrite缓冲区,例如在执行测试的环境中;doublewrite文件所在目录通过系统变量innodb_doublewrite_dir(MySQL 8.0.20中引入)指定,如果不指定则在innodb_data_home_dir目录(默认为data目录)下创建;如果指定doublewrite目录,建议设置在最快的存储介质上,以提高效率;
mysql
root@yudukai:/var/lib/mysql# ll
# ...省略
# 默认创建两个双写缓冲区文件
-rw-r----- 1 mysql mysql 196608 May 29 08:52 '#ib_16384_0.dblwr' # 文件1
-rw-r----- 1 mysql mysql 8585216 Oct 27 2025 '#ib_16384_1.dblwr' # 文件2
# ...省略
命名方式为:
#ib_page_size_file_number.dblwr,以上#ib_16384_0.dblwr的文件表示当前数据页的大小为16KB,编号为0
双写文件的数量通过系统变量innodb_doublewrite_files设置,默认情况下,为每个缓冲池实例创建两个doublewrite文件,也就是说文件数量为innodb_buffer_pool_instances * 2 ;此变量用于高级性能调优,大多数场景使用默认设置即可;
6.9 重做日志 - Redo Log
6.9.1 重做日志的作用?
- 重做日志在保证事务的持久性和一致性方面起到了至关重要的作用
- 重做日志用于在数据库崩溃后恢复已提交事务还没有来的及落盘的数据。重做日志以文件的形式保存在磁盘上,在正常的操作过程中,
MySQL根据受影响的记录进行编码并写入重做日志文件,这些数据称为"Redo",在重新启动时自动读取重做日志进行数据恢复。
重做日志和双写缓冲区的区别:

6.9.1.1 为什么要用Redo Log,而不是直接写磁盘?
- 我们来分析一下,首先明确一点,我们对数据进行的
DML操作都会包含在事务当中,当完成修改并且提交事务之后,在内存中被修改的数据页就要刷新到磁盘完成持久化;- 那么如果这次
DML操作对应的修改开始刷盘的话,当服务器崩溃,没有被刷到磁盘的数据页就从内存中丢失,这时这个事务的修改在磁盘上就是不完整的,也就是没有保证事务的一致性(就是上面那张图)- 为了解决这个问题,
InnoDB在执行每个DML操作时,当内存中的数据页修改完成之后,把修改的内容以日志的形式保证在磁盘上,然后再对数据页进行真正的落盘操作,这样做就相当于对修改进行了一次备份,即使当服务器崩溃也不会受到影响,当服务器重启之后,可以从磁盘上的日志文件中找到上次崩溃之前没有来的及落盘的数据继续执行落盘操作;InnoDB引擎的事务采用了WAL技术(Write-Ahead Logging) ,基本思想是先写日志,再写磁盘,只有日志写入成功,事务才算提交成功,这里的日志就是Redo Log。当发生宕机且数据未刷到磁盘的时候,可以通过Redo Log来恢复,保证ACID中的持久性,这也是Redo Log的作用。UNDO日志保证了事务的原子性,双写缓冲区保证了数据页完整性(页不损坏),REDO日志保证了事务的持久性(已提交事务不丢)
6.9.1.2 Redo Log的写入时机?
当发生数据修改操作时追加重做日志,已落盘数据对应的日志位置被记录为一个检查点,检查点之前的数据被置为无效,所以重做日志文件可以循环使用。以一个更新操作为例,重做日志的写入过程与时机,如下图所示:

6.9.2 Redo Log的格式是怎样的?
6.9.2.1 RedoLog中需要记录哪些内容?
- 当进行
DML操作时,首先要修改内存中的数据页,但是修改的数据有可能只是数据页中很少的一部分内容,甚至有可能只修改了几个字节,那么在RedoLog中是要记录整个数据页吗?当然不是,如果每次保存整个数据页的话就有太多的无用数据写入日志,严重影响效率而且浪费空间- 为了节省空间提高效率,
RedoLog只记录被修改的内容,比如当前的DML修改了哪个表空间、表空间中的哪个数据页,数据页中多少偏移量处的值修改成了什么,比如:
mysql
# 一个例子
1号表空间中的页编号为50的数据页中偏移量为1000处的整型值修改为100
关键修改信息:
- 明确了当前日志对应的文件
- 要修改的起始位置
- 要修改的长度------整型值(
4B)- 要修改的新值
这样就可以用很小的日志记录当前对数据页所做的修改,大 节省了空间
总结:
RedoLog本质上只是记录了事务对数据库做了哪些修改,修改操作包含多种场景,比如对数据行、索引页的增删改,对范围的修改与删除等等,不同场景的redo日志定义了不同的类型,但是绝大部分类型的redo日志都有下边这种通用的结构包括:
Type: 日志类型,1BTYESpace ID: 操作所属的表空间,4BTYEPage no: 操作的数据页在表空间中的编号,4BTYEdata:日志的内容,长度不固定
如下图所示:

6.9.2.2 data 部分的具体内容是什么?
data部分又可以分为:
- 数据页中的偏移量
- 修改内容的长度
- 具体的修改内容
如下图所示:

在这个基础上
InnoDB为了节省空间,根据日志类型做了一些优化。
6.9.3 RedoLog的类型分为哪些?
查看
RedoLog的类型,最完整最直观的方式就是通过阅读源代码,如下所示:
c
/** @file include/mtr0types.h
Mini-transaction buffer global types
Created 11/26/1995 Heikki Tuuri
*******************************************************/
// 省略...
/** @name Log item types
The log items are declared 'byte' so that the compiler can warn if val and type parameters are switched in a call to mlog_write_ulint. NOTE!
For 1 - 8 bytes, the flag value must give the length also! @{ */
enum mlog_id_t {
/** if the mtr contains only one log record for one page,
i.e., write_initial_log_record has been called only once,
this flag is ORed to the type of that first log record */
MLOG_SINGLE_REC_FLAG = 128, # 提供额外信息类型
# 针对数据页的类型,主要是对日志进行优化
/** one byte is written */
MLOG_1BYTE = 1,
/** 2 bytes ... */
MLOG_2BYTES = 2,
/** 4 bytes ... */
MLOG_4BYTES = 4,
/** 8 bytes ... */
MLOG_8BYTES = 8,
# 针对数据页的类型
/** Record insert */
MLOG_REC_INSERT_8027 = 9,
/** Mark clustered index record deleted */
MLOG_REC_CLUST_DELETE_MARK_8027 = 10,
/** Mark secondary index record deleted */
MLOG_REC_SEC_DELETE_MARK = 11,
/** update of a record, preserves record field sizes */
MLOG_REC_UPDATE_IN_PLACE_8027 = 13,
/*!< Delete a record from a page */
MLOG_REC_DELETE_8027 = 14,
/** Delete record list end on index page */
MLOG_LIST_END_DELETE_8027 = 15,
/** Delete record list start on index page */
MLOG_LIST_START_DELETE_8027 = 16,
/** Copy record list end to a new created index page */
MLOG_LIST_END_COPY_CREATED_8027 = 17,
/** Reorganize an index page in ROW_FORMAT=REDUNDANT */
MLOG_PAGE_REORGANIZE_8027 = 18,
/** Create an index page */
MLOG_PAGE_CREATE = 19,
/** Insert entry in an undo log */
MLOG_UNDO_INSERT = 20,
/** erase an undo log page end */
MLOG_UNDO_ERASE_END = 21,
/** initialize a page in an undo log */
MLOG_UNDO_INIT = 22,
/** reuse an insert undo log header */
MLOG_UNDO_HDR_REUSE = 24,
/** create an undo log header */
MLOG_UNDO_HDR_CREATE = 25,
/** mark an index record as the predefined minimum record */
MLOG_REC_MIN_MARK = 26,
/** initialize an ibuf bitmap page */
MLOG_IBUF_BITMAP_INIT = 27,
#ifdef UNIV_LOG_LSN_DEBUG
/** Current LSN */
MLOG_LSN = 28,
#endif /* UNIV_LOG_LSN_DEBUG */
/** this means that a file page is taken into use and the prior
contents of the page should be ignored: in recovery we must not
trust the lsn values stored to the file page.
Note: it's deprecated because it causes crash recovery problem
in bulk create index, and actually we don't need to reset page
lsn in recv_recover_page_func() now. */
MLOG_INIT_FILE_PAGE = 29,
/** write a string to a page */
MLOG_WRITE_STRING = 30,
/** If a single mtr writes several log records, this log
record ends the sequence of these records */
MLOG_MULTI_REC_END = 31,
/** dummy log record used to pad a log block full */
MLOG_DUMMY_RECORD = 32,
# 针对表空间的日志类型
/** log record about creating an .ibd file, with format */
MLOG_FILE_CREATE = 33,
/** rename a tablespace file that starts with (space_id,page_no) */
MLOG_FILE_RENAME = 34,
/** delete a tablespace file that starts with (space_id,page_no) */
MLOG_FILE_DELETE = 35,
/** mark a compact index record as the predefined minimum record */
MLOG_COMP_REC_MIN_MARK = 36,
/** create a compact index page */
MLOG_COMP_PAGE_CREATE = 37,
/** compact record insert */
MLOG_COMP_REC_INSERT_8027 = 38,
/** mark compact clustered index record deleted */
MLOG_COMP_REC_CLUST_DELETE_MARK_8027 = 39,
/** mark compact secondary index record deleted; this log
record type is redundant, as MLOG_REC_SEC_DELETE_MARK is
independent of the record format. */
MLOG_COMP_REC_SEC_DELETE_MARK = 40,
/** update of a compact record, preserves record field sizes */
MLOG_COMP_REC_UPDATE_IN_PLACE_8027 = 41,
/** delete a compact record from a page */
MLOG_COMP_REC_DELETE_8027 = 42,
/** delete compact record list end on index page */
MLOG_COMP_LIST_END_DELETE_8027 = 43,
/*** delete compact record list start on index page */
MLOG_COMP_LIST_START_DELETE_8027 = 44,
/** copy compact record list end to a new created index page */
MLOG_COMP_LIST_END_COPY_CREATED_8027 = 45,
/** reorganize an index page */
MLOG_COMP_PAGE_REORGANIZE_8027 = 46,
/** write the node pointer of a record on a compressed
non-leaf B-tree page */
MLOG_ZIP_WRITE_NODE_PTR = 48,
/** write the BLOB pointer of an externally stored column
on a compressed page */
MLOG_ZIP_WRITE_BLOB_PTR = 49,
/** write to compressed page header */
MLOG_ZIP_WRITE_HEADER = 50,
/** compress an index page */
MLOG_ZIP_PAGE_COMPRESS = 51,
/** compress an index page without logging it's image */
MLOG_ZIP_PAGE_COMPRESS_NO_DATA_8027 = 52,
/** reorganize a compressed page */
MLOG_ZIP_PAGE_REORGANIZE_8027 = 53,
/** Create a R-Tree index page */
MLOG_PAGE_CREATE_RTREE = 57,
/** create a R-tree compact page */
MLOG_COMP_PAGE_CREATE_RTREE = 58,
/** this means that a file page is taken into use.
We use it to replace MLOG_INIT_FILE_PAGE. */
MLOG_INIT_FILE_PAGE2 = 59,
/** Table is being truncated. (Marked only for file-per-table) */
/* MLOG_TRUNCATE = 60, Disabled for WL6378 */
/** notify that an index tree is being loaded without writing
redo log about individual pages */
MLOG_INDEX_LOAD = 61,
/** log for some persistent dynamic metadata change */
MLOG_TABLE_DYNAMIC_META = 62,
/** create a SDI index page */
MLOG_PAGE_CREATE_SDI = 63,
/** create a SDI compact page */
MLOG_COMP_PAGE_CREATE_SDI = 64,
/** Extend the space */
MLOG_FILE_EXTEND = 65,
/** Used in tests of redo log. It must never be used outside unit tests. */
MLOG_TEST = 66,
MLOG_REC_INSERT = 67,
MLOG_REC_CLUST_DELETE_MARK = 68,
MLOG_REC_DELETE = 69,
MLOG_REC_UPDATE_IN_PLACE = 70,
MLOG_LIST_END_COPY_CREATED = 71,
MLOG_PAGE_REORGANIZE = 72,
MLOG_ZIP_PAGE_REORGANIZE = 73,
MLOG_ZIP_PAGE_COMPRESS_NO_DATA = 74,
MLOG_LIST_END_DELETE = 75,
MLOG_LIST_START_DELETE = 76,
/** biggest value (used in assertions) */
MLOG_BIGGEST_TYPE = MLOG_LIST_START_DELETE
};
// 省略...
总结:
RedoLog的类型根据数据操作的不同场景和对日志的优化方式有几十种之多,总体可以分为:
- 用于数据页的日志类型,比如对数据页的修改
- 用于表空间文件的日志类型,比如对表空间的修改
- 提供额外信息的日志类型
6.9.4 不同日志类型对应了哪些操作?
日志类型总体可以分为三大类:
- 用于数据页的日志类型
- 用于表空间文件的日志类型
- 提供额外信息的日志类型
不同的日志类型对应的日志内容也不尽相同,而进行
DML操作时,大多数RedoLog属于用于数据页的日志类型属于用于数据页的日志类型中的几种最常见数据操作所对应的日志类型如下:
MLOG_WRITE_STRING = 30,type对应的值为30,表示在页面的某个偏移量处写入一个字符串,由于字符串的长度不固定,需要用到一个表示长度的区域记录,此时日志内容格式如下图所示:

MLOG_4BYTES = 4,type对应的值为4, 这种类型应用于对固定长度值的修改,比如修改整型字段,由于长度固定,所以用于表示长度的区域可以省略,从而尽可能的减少空间使用,此时日志内容格式如下图所示:

类似的类型还有
MLOG_1BYTE = 1 , MLOG_2BYTES = 2 , MLOG_8BYTES = 8分别表示固定修改1字节、2字节、8字节的数据,日志格式与MLOG_4BYTES = 4相同还有其他一些日志类型,比如:
MLOG_REC_INSERT_8027 = 9,表示写入一条行格式为非紧凑型的记录,对应的行格式为Redundant
MLOG_COMP_REC_INSERT_8027 = 38,表示写入一条行格式为紧凑型的记录,对应的格式为Compact、Dynamic和Compressed
MLOG_REC_UPDATE_IN_PLACE_8027 = 13,表示更新一条记录
MLOG_REC_DELETE_8027 = 14,表示从数据页中删除一条记录
MLOG_LIST_END_DELETE_8027 = 15,表示从索引页中删除最后一条记录
MLOG_LIST_START_DELETE_8027 = 16,表示从索引页中删除第一条记录
MLOG_LIST_END_DELETE_8027和MLOG_LIST_START_DELETE_8027配合可以删除索引页中一个范围的记录,而不用记录每一条记录的删除日志
MLOG_PAGE_CREATE = 19,表示创建一个索引页,这个类型是关于页的类型
属于用于表空间文件的日志类型:
MLOG_FILE_CREATE = 33,表示创建一个.ibd表空间文件
MLOG_FILE_RENAME = 34,表示重命名一个表空间文件
MLOG_FILE_DELETE = 35,表示删除一个表空间文件
属于提供额外信息的日志类型:
MLOG_MULTI_REC_END = 31,只由一个字节的Type构成,用于标识一个MiniTransaction(MTR)的结尾。
Mini-Transaction可以看做是一组原子性的磁盘操作,一个事务由一个或多个MTR组成,后面会详细介绍
总结:
- 不同的日志类型对应的日志内容和作用各不相同
- 用于数据页的日志类型主要记录数据页的修改,比如创建和删除数据页,以及对数据的增删改查操作
- 用于表空间文件的日志类型主要记录对表空间文件的修改
- 提供额外信息的日志类型主要标记一个
Mini-Transaction的结尾
6.9.4.1 如果一个DML操作修改了表中的多个字段,日志如何表示?
上面的分析过程介绍了简单的
DML操作对应的日志,通常情况下,一个DML操作会修改表中的多个字段,也可能修改多条记录,对于正常的增删改对应不同的日志类型,对应日志所包含的主要信息如下图所示:

- 新增操作:主要包含数据页内的偏移量,主键信息,新增的字段个数,每个字段的内容的实际长度,具体的内容等
- 删除操作:主要包含数据页内的偏移量,主键信息、旧事务的
Id,旧roll_pointer,是否删除标识- 更新操作:主要包含数据页内的偏移量,主键信息、旧事务的
Id,旧roll_pointer,被更新字段的数据,每个被更新字段的实际长度,更新的具体内容
小结:以上介绍了几种常见的
Redo Log格式,如果没有开发日志解析工具的需求不用过多研究。Redo Log的作用是把事务在执行过程中对数据库所做的所有修改都记录下来,以便在系统崩溃重启后可以把事务所做的修改都恢复出来