Mysql日志篇(redo log)

155 阅读9分钟

基本概念

  1. 实现事务的持久性(事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失).
  2. Redo log 是重做日记,属于InnoDB引擎的日记。是物理日记,日记记录的内容的是数据页的更改。记录的是"物理级别"上的页修改操作,比如页号xx、偏移量yyy写入了’zzz’数据。主要为了保证数据的可靠性。
  3. 前滚操作:具备crash-safe能力,提供断电重启时解决事务丢失数据问题。
  4. 提高性能: 先写Redo log记录更新。当等到有空闲线程、内存不足、Redo log满了时 “刷脏”。写Redo log是顺序写入,刷脏是随机写(将数据从buffer pool刷新到os buffer(也叫page cache,文件系统缓存), 在调用fsync刷盘到磁盘),节省的是随机写磁盘的 IO 消耗(转成顺序写),所以性能得到提升。此技术称为WAL技术:Write-Ahead Logging,它的关键点就是先写日记磁盘,再写数据磁盘。

redo log工作过程

当数据库对数据做修改的时候,需要把数据页从磁盘读到buffer pool中,然后在buffer pool中进行修改,那么这个时候buffer pool中的数据页就与磁盘上的数据页内容不一致,此时buffer pool的数据页为dirty page 脏数据,如果这个时候发生非正常的DB服务重启,那么这些数据还在内存,并没有同步到磁盘文件中(注意,同步到磁盘文件是个随机IO),也就是会发生数据丢失,如果这个时候,能够在有一个文件,当buffer pool 中的数据页变更结束后,把相应修改记录记录到这个文件(注意,记录日志是顺序IO),那么当DB服务发生crash,进行恢复DB的时候,可以根据这个文件的记录内容,重新持久化刷新到磁盘文件,保持数据的一致性。

这个文件其实就是redo log ,用于记录数据修改后的记录,顺序记录,主要用于数据的持久化操作。

作用

  1. 保证事务的持久性。如果buffer pool缓冲池中的脏页【脏数据】还没有进行刷盘的时候,此时数据库发生crash,重启服务后,我们可以通过redo log日志找到需要重放到磁盘文件的那些数据记录。
  2. 提高事务提交的速度。buffer pool缓冲池中的数据直接刷新到磁盘,是一个随机IO,效率较差,而把buffer pool中的数据记录到redo log,是一个顺序IO,可以提高事务提交的速度。

redo log的组成

  • redo log buffer日志缓存:重做日志缓存(redo log buffer)存在于内存中。
  • redo log file日志文件:重做日志文件(redo log file)存在于磁盘中。

为了确保每次日志都能写入到事务日志文件中,在每次将log buffer中的日志写入日志文件的过程中都会调用一次操作系统的fsync操作(即fsync()系统调用)。

要写入到磁盘上的log file中(redo:ib_logfileN文件,undo:share tablespace或.ibd文件),中间还要经过操作系统内核空间的os buffer,调用fsync()的作用就是将OS buffer中的日志刷到磁盘上的log file中。

redo log的工作原理

redo log记录的是新数据的备份。在事务提交前,只要将redo log持久化即可。

数据不需要在事务提交前写入磁盘,而是缓存在内存中。

为了保证持久性,必须在事务提交前将redo log日志持久化。

redo log的相关参数

innodb_flush_log_at_trx_commit

控制commit动作是否刷新log buffer到磁盘。该变量有3种值:0、1、2,默认为1。

  • 0:每次提交事务都是先写入到redo log buffer, 然后每隔一秒写入到os buffer,接着fsync将os buffer中的数据刷盘到redo log file中。(如果系统崩溃,会丢失1秒钟的数据)
  • 1: 每次提交事务先写入到redo log buffer, 接着写入到os buffer,最后调用fsync刷盘,这里面的所有步骤都是同步的,任意一步失败,事务就会失败。
  • 2: 每次事务提交,数据不写到log buffer,仅写入到os buffer,由os自己决定什么时候刷盘。

InnoDB存储引擎有一个后台线程,每隔1秒,就会把 Redo Log Buffer 中的内容写到文件系统缓存( page cache ),然后调用刷盘操作。

也就是说,一个没有提交事务的Redo Log记录,也可能会刷盘。因为在事务执行过程Redo Log记录是会写入Redo Log Buffer 中,这些Redo Log记录会被后台线程刷盘。

除了后台线程每秒1次的轮询操作,还有一种情况,当Redo Log Buffer占用的空间即将达到innodb_log_buffer_size(这个参数默认是16M)的一半的时候,后台线程会主动刷盘。

正常情况下,设置参数值为0或者2能提供插入的效率,但在故障的时候可能会丢失1秒钟数据。 并且参数值为2和0的时候差距并不大,因为它们都是每秒从os buffer刷到磁盘,它们之间的时间差体现在log buffer刷到os buffer上。因为将log buffer中的日志刷新到os buffer只是内存数据的转移,并没有太大的开销,所以每次提交和每秒刷入差距并不大。

其余参数

innodb_log_buffer_size: 指定 log buffer【redo log缓存区】的大小,默认16M
innodb_log_file_size: 决定每个 redo log(ib_logfile0...n) 文件的大小,较大的文件减少日志切换频率,适合高负载系统,默认8m。
innodb_log_files_in_group:控制日志文件组的数量,2 是默认值,增加日志文件组数量有助于减少日志切换的频率。
    较多的 redo log 文件:有助于减轻日志文件的频繁切换压力,从而提供更好的性能,但增加崩溃恢复的时间。
    较少的 redo log 文件:会导致更频繁的日志切换,可能对性能产生不利影响。

可能会触发redo log写文件的几种场景

  1. Redo log buffer空间不足时
  2. 事务提交
  3. 后台线程
  4. 做checkpoint
  5. 实例shutdown时
  6. binlog切换时

redo log和binglog的事务判断是否已经提交?

两者的原理类似,事务提交时都会在对应的log(redolog, binlog)追加一个特殊标记,表明该事务已经完成。

redo log的容灾恢复过程

在崩溃恢复时,InnoDB 使用 redo log 的 提交标记 和 LSN 来判断哪些事务需要恢复或回滚。

  1. 从 redo log 的起始位置开始,逐条扫描日志条目;
  2. 如果发现某个事务的最后一条日志是 COMMIT 标记,则该事务已经提交,执行重做。
  3. 如果某个事务没有 COMMIT 标记,说明事务未提交,InnoDB 会根据 undo log 回滚该事务的所有修改。

恢复的关键点:

  1. 未持久化的事务数据:在崩溃恢复时,redo log 会被用来恢复未写入磁盘的数据。即使数据页未完全刷新,redo log 可以确保事务的一致性。
  2. 回放未提交的事务:在数据库崩溃后,回放 redo log 时,只有那些已经提交的事务才会被恢复。而那些未提交的事务则会被回滚。

Binlog 与 Redo Log 的两阶段提交

在 MySQL 中,BinlogRedo Log 的两阶段提交机制(Two-phase Commit)是为了解决日志一致性问题,确保在发生故障时,Binlog 和 Redo Log 的状态是一致的。这一机制是事务在崩溃恢复后依然能够保持数据完整性的关键。

两阶段提交的背景

  • BinlogRedo Log 分别属于 MySQL Server 层InnoDB 存储引擎层
  • Binlog 用于主从复制和增量恢复,记录的是逻辑操作(如 SQL 语句)。
  • Redo Log 用于崩溃恢复(Crash Recovery),记录的是数据页的物理变更。
  • 如果在事务提交过程中,Binlog 写入成功但 Redo Log 未完成提交,或者反过来,系统发生崩溃可能导致数据不一致。

两阶段提交的目标

  • 确保 BinlogRedo Log 的一致性。
  • 保证在崩溃恢复后,事务的最终状态正确(要么完全提交,要么完全回滚)。

两阶段提交的执行流程

以下是一个事务在两阶段提交中的详细执行步骤:

第一阶段:Prepare 阶段
  1. 记录 Redo Log 的 Prepare 状态

    • 当事务即将提交时,先将所有的变更写入到 Redo Log 中,但此时标记为 “Prepare 状态” ,并刷盘。
    • 此步骤保证 Redo Log 在崩溃恢复时,能够检测到事务是否完成。
    • 同时生成一个事务 ID(XID)用于唯一标识该事务。
  2. 记录 Binlog

    • 将事务的逻辑操作写入 Binlog 中,并刷盘。
    • Binlog 的写入发生在 Redo Log 的 Prepare 状态之后,以确保 Binlog 不会记录失败的事务。
第二阶段:Commit 阶段
  1. 提交 Redo Log

    • 当 Binlog 写入完成并刷盘成功后,将 Redo Log 的状态从 Prepare 改为 Commit,并刷盘。
    • 此时事务才真正完成提交。

两阶段提交的核心逻辑

在崩溃恢复时,根据 Redo Log 和 Binlog 的状态判断事务是否提交:

  • 如果 Redo Log 处于 Prepare 状态,但 Binlog 没有完整记录,事务未提交,需回滚。
  • 如果 Redo Log 和 Binlog 都完整记录,事务已提交,需完成 Redo Log 的提交。

为什么需要两阶段提交

单独使用 Binlog 的问题
  • Binlog 记录的是逻辑操作,无法直接用于崩溃恢复。如果系统崩溃后,只根据 Binlog 无法知道事务的 Redo Log 是否已刷盘。
单独使用 Redo Log 的问题
  • Redo Log 是物理日志,记录的是数据页的物理变更,无法用于主从复制或增量恢复。
两阶段提交的必要性

两阶段提交通过协调 Binlog 和 Redo Log 的写入顺序,保证二者的一致性,从而既支持崩溃恢复,又支持主从复制。

两阶段提交的示例

假设有一个事务 INSERT INTO users VALUES (1, 'Alice')

事务执行流程
  1. 将数据写入内存(Buffer Pool)。
  2. 写 Redo Log(Prepare 状态),并刷盘。
  3. 写 Binlog,并刷盘。
  4. 更新 Redo Log 状态为 Commit,并刷盘。
崩溃恢复情况
  • 如果 Redo Log 处于 Prepare 状态,检查 Binlog:

    • Binlog 存在:Redo Log 提交事务。
    • Binlog 不存在:Redo Log 回滚事务。