代码改变世界

实用指南:日志(Redo Log、Undo Log、Binlog)

2025-12-26 14:37  tlnshuju  阅读(0)  评论(0)    收藏  举报

目录

背景知识:

Redo Log(重做日志)

Undo Log(回滚日志)

帮助实现多版本并发控制MVCC 的原理:

如何理解MVCC

BinLog(二进制日志)

BinLog的三种格式

三大日志的核心作用


背景知识:

  1. MySQL 分为两层 : Server 层(负责 MySQL 作用)和 存储引擎层(负责数据存储)
  2. 没有的就是Redo Log 和 Undo Log 属于存储引擎层 InnoDB引擎 的日志,其他引擎
  3. 属于 Server 层的日志就是Binlog

Redo Log(重做日志)

问题引入

  • 如果MySQL的每一次修改数据都必须写入磁盘,那么每一次操作就对应着一次磁盘IO,成本很高。
  • 如果MySQL的每一次修改资料都将脏页暂存在内存里,然后延迟异步刷盘,可能系统突然崩溃,导致内存的脏页数据丢失,此时就不能保证事务的持久性。

脏页:内存中被修改,但尚未同步到磁盘的数据页

解决方案:为了解决上述挑战,既要保证性能,又要保证持久性,MySQL的设计者就采用了WAL技能,WAL即 Write-Ahead Logging,关键点是 先写日志, 写的就是RedoLog日志

在事务提交时,将日志写入Redo Log磁盘文档,记录下数据页的物理修改。当系统突然崩溃时,脏页材料丢失,可能根据Redo Log找回脏页数据

问题

那么 直接把脏页数据写入磁盘把日志记录写入磁盘不都是一次磁盘IO吗?为什么选择后者?

RedoLog 是固定大小的文件,以循环的方式写入,不断覆盖最早的内容

因为 把脏页数据写入磁盘是随机IO,而把日志记录写入RedoLog文件中是顺序IO

顺序IO效率远大于随机IO

  • 记录的内容:数据页的物理修改
  • 写入时机:事务提交时
  • 用途:保证事务的持久性

假设执行一条 SQL:

UPDATE accounts SET balance = balance - 100 WHERE id = 1;

  1. InnoDB 找到 id=1 的数据所在的数据页(假设是页 5)。
  2. 修改页 5 中的 balance 字段值(从 500 改为 400)。
  3. 生成一条 redo log,记录:“页 5 的偏移位置 X,值从 500 变为 400”。
  4. 事务提交时,redo log 被刷盘,但数据页可能仍在内存中(未刷盘)。
  5. 若此时数据库崩溃,重启后通过 redo log 重放,将页 5 的修改重新应用到磁盘。

Undo Log(回滚日志)

用途:保证事务的原子性 和 支持实现MVCC(多版本并发控制)

保证事务原子性的原理:

逻辑日志,记录了数据修改管理的“反向操作”,当事务要求回滚时,MySQL可以依据反向管理将数据恢复到事务开始时的状态,从而保证事务的原子性。就是Undo Log

比如,如果一个事务修改了一行数据,将某列的值从 100 修改为 200,Undo Log 会记录一个反向操作,表示将 200 恢复为 100,从而撤销这一修改。

支持达成多版本并发控制MVCC 的原理:

Undo Log 记录了资料的版本链

1.版本链的构建:

  • 每次修改素材行时,会生成一个 Undo Log 记录,含有:
    • 旧版本的材料镜像
    • 事务 ID(trx_id):标识生成该版本的事务。
    • 回滚指针(roll_pointer):指向更早版本的 Undo Log。
  • 多个版本通过 roll_pointer 形成链表结构,称为版本链

2.MVCC 的读操作:

  • 当某事务需要读取数据时,InnoDB 会根据当前事务的视图(ReadView)的信息与数据版本的trx_id去比较,从而判断哪些版本可见,如果最新版本不可读,则会根据 Undo Log 的版本链去获得上一版本的数据
如何理解MVCC

背景知识:

  • MySQL会为每个事务分配一个事务ID,事务ID按事务创建顺序递增

MVCC多版本并发控制,核心思想:维护一个数据的多个版本,使得读写操作没有冲突,避免加锁阻塞

典型应用:MySQL 的 InnoDB 存储引擎经过 MVCC 实现读已提交可重复读 隔离级别。

MVCC的构建依赖于

  • 数据库记录行的隐藏字段 trx_id 事务id 和 roll_pointer 指针
  • ReadView读视图
  • UndoLog版本链

实现原理:

首先,InnoDB 在事务开启后执行第一个查询时,会创建一个快照(称之为ReadView),该ReadView包含了以下信息:

m_ids: 活动事务id列表(活动事务指的是已经开始、尚未提交/回滚的事务)

min_trx_id: 最小活动事务id

min_trx_id 是 ReadView 创建时 “所有活跃事务的最小 ID”—— 若某个事务的 ID 比 min_trx_id 还小,说明它在所有活跃事务之前就结束了(已提交 / 回滚),其修改的内容是 “稳定的”

max_trx_id: 最大活动事务id

creator_trx_id: 当前事务id

紧接着 InnoDB 会通过查询语句定位到最新版本的材料行,并根据以下规则获取到能够访问的材料版本:

  • 如果被访问版本的trx_id 事务id等于ReadView中的当前事务id通过,表明当前事务在访问自己修改过的记录,即能够读取到该版本的资料;
  • 如果被访问版本的trx_id 事务id小于ReadView中的最小活动事务id,表明生成该版本的事务在当前事务生成ReadView前已经提交,即可以读取到该版本的数据;
  • 如果被访问版本的trx_id 事务id大于或等于ReadView中的最大活动事务id,表明生成该版本的事务在当前事务生成ReadView后才开启,此时该版本不可以被当前事务访问,得通过隐藏的回滚指针从undo log中读取历史版本;
  • 如果被访问版本的trx_id 事务id,在ReadView的最小活动事务id 和 最大活动事务id 之间,则需要判断trx_id 事务id 是否在活动事务列表中
    • 假设在:说明ReadView创建时,创建该版本内容的事务还未提交,因此得通过回滚指针读取历史版本并返回;
    • 如果不在:说明ReadView创建时,创建该版本数据的事务已经提交,所以允许读取到该版本的数据。

读已提交隔离级别下:事务中的每次查询前都会创建一个新的ReadView。新建的ReadView会更新creator_trx_id以外的其余字段,因此不可重复读现象依然存在。

可重复读隔离级别下:只会在事务中的第一次查询时创建ReadView,同一个事务中后续所有的查询共用一个ReadView,由此便解决了不可重复读的问题。

ReadView的生成时机是不同的,这也是两个隔离级别有区别的根本原因

BinLog(二进制日志)

BinLog: 是记录所有数据库表结构变更 以及 表数据修改的二进制日志,不会记录Select和Show处理。

用途:数据备份、灾难恢复、主从同步

写入时机:事务提交时

BinLog的三种格式

MySQL 的BinLog协助三种格式: Statement、Row、Mixed

  • Statement
    • BinLog里面记录的是 SQL 语句原文
    • 这种格式有bug,例如采用now()等时间函数,导致主从同步后数据不相同
  • Row
    • BinLog里面记录的是 信息的具体变化
    • 例如 会记录修改的表名称、操作类型
      • Insert运行 Row格式会记录新插入的行的 所有字段列的值
      • Update执行 Row格式会记录行 更新前后的 所有字段列的值
      • Delete运行 Row格式会记录行删除前的 所有字段列的值
    • 优点:可以更好的保证主从数据同步时数据一致性,避免了STATEMENT格式中可能出现的不一致性问题
    • 缺点
      • 需要记录的内容更多,例如批量修改、插入等,就需要把每条记录都记录,存在性能困难
      • 文件体积大,在主从同步时,网络IO更高,耗费时间长
  • Mixed
    • 由MySQL根据具体情况自主选择使用Statement格式或Row格式来记录
    • 默认利用Statement格式,记录SQL语句
    • 在必要时候,对于某些可能导致主从不一致的SQL语句,自动使用Row格式,记录数据变更
    • 很好理解,这是为了平衡 性能和一致性

三大日志的核心作用

日志类型

核心作用

存储内容

所属组件

redo log

保证事务持久性(ACID 中的 D),崩溃恢复

物理日志:内容页的修改记录

InnoDB 存储引擎

undo log

保证事务原子性(ACID 中的 A),支持回滚

逻辑日志:事务修改前的反向运行

InnoDB 存储引擎

binlog

用于主从同步和时间点恢复(PITR)

逻辑日志:SQL 语句或行修改记录

MySQL 服务器层