前言
你思考过 InnoDB 是如何实现事务的ACID(原子性、持久性、隔离性和一致性)吗?而我们今天要讲的 redo log 就是用来实现 InnoDB 事务中的原子性和持久性。
原子性:一个事务的操作要么都成功,要么都失败。
持久性:当事务提交后,确保都已经持久化,不会导致数据丢失(硬件异常除外)
如何实现?
在一个事务中,有多个数据行需要修改,将对应数据页修改后,再同步刷新到磁盘,需要保证原子操作、同时数据页都修改成功才算整个事务的成功。
如何保证原子性?
假设一个事务有3个更新语句,执行到第2个的时候系统崩溃了,这种情况如何确保原子性?你说可以回滚第一条成功的数据或者重试第2、3条语句。
考虑到这3条语句对应的记录处于不同的数据页,需要利用磁盘随机寻址,性能极低。
因此,我们可以通过顺序写的方式,将这3条语句改动后的物理数据页(比如将数据页偏移量x位置的值改为'aaaa')先全部记录下来,顺序写速度很快,当这3条语句对应的数据页都已经完全记录下来后,我们可以认为成功。
接着再将脏页数据(改动的数据)同步到数据页,如果数据库系统发生崩溃,借助 redo log 进行数据恢复,确保原子性和持久性。
持久性是否保证? redo log 是物理文件,当完成刷盘可以确保持久性。
原子性如何保证?3条修改语句要么同时成功,要么同时失败,当修改语句全部写入 redo log 完成事务提交后,即使后期脏页数据同步到数据页崩溃了,借助于 redo log 恢复未执行完成的事务。
崩溃后从 redo log 哪个位置开始恢复?
答案是:检查点(Checkpoint)
检查点的组成
-
日志序列号(LSN) :
- 检查点记录了一个日志序列号(LSN),这是一个唯一标识重做日志记录的编号。
- 这个LSN标记了在检查点时,所有事务的更改都已经被持久化到数据文件中。
-
缓冲池状态:
- 检查点还记录了缓冲池中脏页的状态。这包括哪些页是脏页以及它们的LSN。
- 在检查点时,数据库系统会尝试将这些脏页刷新到磁盘。
-
其他元数据:
- 检查点可能还包括其他与系统状态相关的信息,如活动事务列表等。
检查点的作用
-
标记持久化状态:
- 检查点标记了在该点之前的所有事务都已经被持久化到数据文件中,因此在崩溃恢复时可以从这个点之后开始重做。
-
加速恢复过程:
- 通过记录检查点,数据库系统在崩溃后可以快速定位需要重做的日志记录,避免从头开始扫描整个重做日志。
-
减少恢复时间:
- 定期创建检查点可以减少崩溃恢复所需的时间,因为只需要从最近的检查点开始重做日志。
Redo Log
InnoDB 是 MySQL 的一种存储引擎,它使用重做日志(Redo Log)来确保数据的 持久性和一致性。InnoDB 的重做日志是其崩溃恢复机制的核心部分。
redo log 记录的是物理操作日志,每个事务对应多个日志条目,并且事务的 redo log 是并发写入并非在事务提交时写入,因此在文件记录的顺序并非是事务开始的顺序。
1. 日志结构
- 日志文件:InnoDB 的重做日志由一组固定大小的日志文件组成,通常称为 ib_logfile0 和 ib_logfile1。这些文件循环使用。
- 日志缓冲区:在内存中有一个日志缓冲区,用于暂时存储日志条目,然后定期刷新到磁盘上的日志文件中。
2. 工作机制
- 写入顺序:事务在执行过程中,所有更改都会首先记录到重做日志缓冲区中。
- 提交事务:当事务提交时,InnoDB 会将重做日志缓冲区中的内容刷新到磁盘,以确保事务的持久性。
- 崩溃恢复:在系统崩溃后,InnoDB 使用重做日志来重做所有已提交但未持久化到数据文件中的事务。
3. 配置参数
-
innodb_log_file_size:设置每个重做日志文件的大小。较大的日志文件可以减少日志切换的频率,但可能会增加恢复时间。
-
innodb_log_buffer_size:设置日志缓冲区的大小。较大的缓冲区可以减少磁盘 I/O,但会占用更多内存。
-
innodb_flush_log_at_trx_commit:控制事务提交时日志的刷新行为。常见的值有:
- 0:每秒将日志缓冲区刷新到磁盘。
- 1:每次事务提交时将日志缓冲区刷新到磁盘(默认,最安全)。
- 2:每次事务提交时将日志缓冲区刷新到操作系统缓存,每秒刷新到磁盘。
4. 配置
以下是一个示例的 MySQL 配置文件片段, InnoDB 重做日志相关的参数:
[mysqld] innodb_log_file_size = 512M
innodb_log_buffer_size = 16M
innodb_flush_log_at_trx_commit = 1
5. 性能与恢复
- 性能:适当配置重做日志的大小和刷新策略可以显著提高数据库的性能,特别是在高事务负载的情况下。
- 恢复时间:较大的重做日志文件可能会增加崩溃后的恢复时间,因为需要处理更多的日志条目。
通过合理配置和使用 InnoDB 的重做日志,您可以在性能和数据安全性之间取得良好的平衡。
Redo log 恢复过程
redo log 记录的物理数据页?
重做日志(Redo Log)主要记录的是事务对数据库的修改数据,属于物理页,但不是整个物理数据页的内容。比如记录数据页偏移量x设置为 'aaaa'。
重做日志记录的关键点:
- 日志记录:重做日志通常记录的是逻辑上的修改,例如“将某个字段的值从A改为B”。它不记录整个数据页的内容,而是记录修改的具体细节。
- 增量变化:重做日志只包含增量变化,即事务对数据的具体修改。这些变化可以是插入、更新或删除操作。
- 未修改的数据不记录:未被事务修改的数据不会被记录到重做日志中,因为重做日志的目的是在崩溃恢复时重做已提交的事务,而未修改的数据不需要重做。
- 物理页信息:虽然重做日志不记录完整的物理数据页,但它会包含一些元数据,如数据页的标识符和偏移量,以便在恢复时能够准确定位需要重做的修改。
通过这种方式,重做日志能够高效地记录事务的修改,并在系统崩溃后快速恢复数据库的状态。这样设计的好处是减少了日志的大小,提高了写入性能,同时也简化了崩溃恢复过程。
redo log 与 脏页
在 InnoDB 中,事务提交和脏页写入磁盘的时机是分开的,这正是重做日志(Redo Log)和缓冲池(Buffer Pool)机制的核心设计之一。
事务提交和脏页写入的时机
-
重做日志提交:
- 当一个事务提交时,InnoDB 会将该事务的修改记录写入重做日志文件。这是事务提交的关键步骤。
- 一旦重做日志被成功写入磁盘,事务就被认为是已提交的。这意味着即使系统崩溃,事务的修改也可以通过重做日志进行恢复。
-
脏页的写入:
- 脏页是指在内存中的缓冲池中已被修改但尚未写入磁盘的数据页。
- 脏页的写入并不需要在事务提交时立即进行。相反,InnoDB 会在后台异步地将脏页写入磁盘。这通常由后台线程在系统空闲时或缓冲池需要腾出空间时进行。
- 这种设计允许 InnoDB 在事务提交时不必等待数据页写入磁盘,从而提高了事务处理的性能。
为什么这样设计?
- 性能优化:通过将事务提交和数据页写入分开,InnoDB可以更高效地处理事务提交,因为写入重做日志通常比写入数据页要快得多。
- 数据完整性:即使在脏页尚未写入磁盘时发生崩溃,重做日志仍然可以用来恢复已提交的事务,确保数据的一致性。
因此,事务提交时,重做日志已经持久化,而脏页的写入则是一个异步的、延迟的过程。这种设计在性能和数据完整性之间取得了良好的平衡。
脏页与双写机制
双写机制(Doublewrite Buffer)是在脏页同步到磁盘的过程中使用的。它的主要目的是确保数据页在写入磁盘时的完整性,防止部分写入问题。以下是双写机制在脏页写入磁盘时的具体应用:
双写机制的工作流程
-
脏页准备:
- 当 InnoDB 决定将脏页从缓冲池写入磁盘时,这些脏页首先会被写入到双写缓冲区(Doublewrite Buffer)。
-
写入双写文件:
- 双写缓冲区中的数据页会被顺序写入到一个特殊的双写文件中。这个文件通常是一个连续的磁盘空间,减少了部分写入的风险。
-
写入实际数据文件:
- 一旦数据页成功写入双写文件,InnoDB 会将这些页从双写文件写入到实际的数据文件(如.ibd文件)。
为什么需要双写机制?
- 防止部分写入:在将数据页直接写入磁盘时,可能会因为系统崩溃或其他原因导致部分写入(Partial Page Write),这会导致数据页损坏。双写机制通过先写入一个连续的双写文件,再写入实际数据文件,确保即使发生部分写入,数据页也能从双写文件中恢复。
- 数据完整性:通过双写机制,即使在写入过程中发生崩溃,InnoDB也可以从双写文件中恢复完整的数据页,确保数据的一致性和完整性。
因此,双写机制是专门为了解决脏页写入磁盘时的完整性问题而设计的,确保在各种故障情况下数据页的可靠性。
崩溃恢复有 redo log?为何还需要 双写机制?
重做日志(Redo Log)确实可以用于恢复未完整写入的数据页,但双写机制并不是鸡肋,它在特定情况下提供了额外的保护和效率。
双写机制是确保数据页写入的完整性。
重做日志的恢复能力
- 重做日志:在系统崩溃后,重做日志可以用于重做已提交事务的修改。这意味着即使数据页在崩溃时未完整写入,重做日志可以帮助恢复这些修改。
双写机制的优势
-
防止部分写入问题:
- 在某些情况下,数据页可能会因为系统崩溃或磁盘故障而部分写入。这种情况下,数据页可能会处于不一致的状态。
- 双写机制通过先将数据页写入一个连续的双写文件,确保即使发生部分写入,数据页也能从双写文件中恢复。这减少了依赖重做日志进行复杂恢复的需求。
-
减少恢复时间:
- 如果没有双写机制,系统在崩溃后需要通过重做日志重做所有未完整写入的数据页的修改,这可能会增加恢复时间。
- 双写机制可以直接从双写文件中恢复完整的数据页,减少了重做日志的使用,从而加快了恢复过程。
-
数据页的完整性:
- 双写机制确保数据页在写入磁盘时的完整性,防止因部分写入导致的数据页损坏。这在某些高负载或大数据页写入的场景下尤为重要。
结论
虽然重做日志可以用于恢复未完整写入的数据页,但双写机制提供了额外的保护,特别是在防止部分写入问题和加快恢复时间方面。
它们的结合使用确保了数据库系统在崩溃恢复时的高效性和可靠性。因此,双写机制并不是鸡肋,而是对数据完整性和系统恢复能力的重要补充。
小结
redo log 保证了事务的 原子性 和 持久性,当事务执行过程中,如果数据库系统发生了崩溃,脏页(修改的数据页)还未同步到数据页,可以通过 redo log 进行恢复。
双写机制保证了数据的 完整性,发生时机在 redo log 事务提交之后,脏页将同步到磁盘时,避免数据部分写入而导致的异常。比如脏页大小 16k,而写入 4k 后就已经发生了崩溃,此时可以里通过双写进行恢复。