浅谈 MySQL 的 Undo log 日志

文章详细介绍了InnoDB存储引擎中UndoLog的存储结构,包括FIL_PAGE_UNDO_LOG页的组成部分,如FileHeader、TRX_UNDO_PAGE_TYPE等。同时,文章阐述了不同类型的操作(INSERT、DELETE、UPDATE)对应的UndoLog记录格式,以及版本链的概念,它是实现不同事务隔离级别的重要机制。

undo log 存储在类型为 FIL_PAGE_UNDO_LOG 页中。 可以从系统表空间中分配空间,也可以从 undo tablespace 中分配空间。 FIL_PAGE_UNDO_LOG 类型页主要分为四部分:

  • File Header,所有页都有的结构
  • Undo Page Header
    • TRX_UNDO_PAGE_TYPE,存储什么类型的 undo log,分类后面会介绍。 不同类型的 undo log 不能存储在同一个页中。 TRX_UNDO_INSERT_REC 类型的放一个页面中,其他类型的放一个页面中。
    • TRX_UNDO_PAGE_START,本页中 undo log 开始的偏移量。
    • TRX_UNDO_PAGE_FREE,本页中,空闲位置开始的偏移量,与最后一条 undo log 的末尾偏移量一样。
    • TRX_UNDO_PAGE_NODE,12 字节,包括4部分内容
      • Pre Node Page Number 和 Pre Node Offset 组成了指向前一个 FIL_PAGE_UNDO_LOG 页的指针;
      • Next Node Page Number 和 Next Node Offset 组成了指向后一个 FIL_PAGE_UNDO_LOG 页的指针;
  • 用于存放 undo log 日志记录的空间
  • File Trailer,所有页都有的结构

01-INSERT 对应的 undo log 记录格式

插入区分乐观插入、悲观插入,

  • 乐观插入,指要插入页的空间充足,足以容纳新插入的记录。
  • 悲观插入,指要插入页的空间不足,需要页分裂。

不管如何,是将一条记录插入。 它对应的回滚操作,就是删除这条插入的记录,TRX_UNDO_INSERT_REC:

  • end of record
  • undo type,TRX_UNDO_INSERT_REC,说明为 INSERT 语句的 undo log。
  • undo no,序列号。
  • table id,可以从 information_schema.INNODB_TABLES 中查询到。
  • 主键各列信息,{<len, value>}。INSERT 相对应的回滚日志是删除插入的记录,所以这里要记录的信息需要包含如何定位到这条记录。
  • start of record

其中,start of record 和 end of record 是两个指针,指向了 undo log 记录开始、结束的位置。

02-DELETE 对应的 undo log 记录格式

在介绍 记录 时有提到,数据页中所有的记录通过 next_record 串联起来,形成一个链表。 所有的被标记为删除(即 delete_mask 为1)的记录也会通过 next_record 串联起来,形成垃圾链表。 数据页的 Page Header 中有一个 PAGE_FREE 属性,指向垃圾链表的头部。

首先,我们要先搞明白 DELETE 语句的执行过程。删除分为两个阶段:

  1. 在聚簇索引上根据 id 找到对应的记录,将其 delete_mask 置为1(同时也会修改隐藏列 DB_TRX_ID 和 DB_ROLL_PTR)。此阶段也被称为 delete mark 阶段。
  2. 当执行删除语句的事务提交后,有专门的线程()将 delete_mask 标记为1的记录从正常记录中移除,加入到垃圾链表中(头插)。 同时需要修改一些页面的其他信息,比如页面中的用户记录数量 PAGE_N_RECS、上次插入记录的位置 PAGE_LAST_INSERT、垃圾链表头节点的指针 PAGE_FREE、页面中可重用的字节数量 PAGE_GARBAGE、还有页目录的一些信息等等。 此阶段也被称之为 purge 阶段。

delete mask 阶段对应的 undo log 类型为 TRX_UNDO_DEL_MARK_REC:

  • end of record
  • undo type,TRX_UNDO_DEL_MARK_REC,说明为 delete mask 阶段的 undo log。
  • undo no,序列号。
  • table id
  • info bits
  • old trx_id
  • old roll_pointer,和 old trx_id 一块,记录了修改 delete_mask 前,记录上的值。
  • 主键各列信息,{<len, value>},主要为了定位记录。
  • index_col_info len,记录了当前、以及下部分内容占用的空间。
  • {<pos, len, value>},保存了记录中的列(在某个索引中),其在索引中的位置、空间占用、实际值。 这部分内容主要在 purge 阶段使用。
  • start of record

03-UPDATE 对应的 undo log 记录格式

UPDATE 语句的处理方式根据是否更新主键分为两种不同的方案:

  1. 不更新主键。 根据是否改变记录的大小,又可以进一步分为:

    • 就地更新,记录大小不发生变化。 对记录中的每个列来说,其大小都不发生改变。
    • 先删除,再插入。 对记录中的每一列来说,任一列的大小发生了变化。 执行的操作是,先删除、再插入。 删除是彻底删除,并不是 DELETE 中分两阶段的删除。 与 purge 阶段不同的事,并非是某个特定线程去删除,而是由用户线程同步删除。

    针对不更新主键的情况,undo log 类型为 TRX_UNDO_UPD_EXIST_REC(省略了 end of record 之类的共有的信息):

    • old trx_id
    • old roll_pointer
    • 主键各列信息,{<len, value>},主要为了定位记录。
    • n_updated,记录了共有多个列被更新了。
    • {<pos, old_len, old_value>} 记录了被更新的列的旧值
    • index_col_info len,记录了当前、以及下部分内容占用的空间。
    • {<pos, len, value>},保存了记录中的列(在某个索引中),其在索引中的位置、空间占用、实际值。
  2. 更新主键。分为两步处理:

    1. 对旧记录进行 delete_mask 阶段的操作。
    2. 插入新的记录。

    所以,这种情况会产生两条 undo log 记录。

04-版本链

在学习 InnoDB 行记录格式时,我们知道在聚簇索引中记录的数据部分 InnoDB 会自动插入三列:DB_ROW_ID\DB_TRX_ID\DB_ROLL_PTR。 其中 DB_ROW_ID 不是必需的,只在没有指定主键且没有唯一列时插入。 其余两列与事务及一致性读视图相关:

  • DB_TRX_ID,每次一个事务对某条聚簇索引记录改动时,都会将事务的 ID 赋给隐藏的 DB_TRX_ID 列
  • DB_ROLL_PTR,每次一个事务对某条聚簇索引记录改动时,旧版本会被写入到 undo log 中,该隐藏列就像一个指针,通过它来找到之前的版本。

对某条记录的每次修改(UPDATEDELETE),都会在 undo log 中记录一个旧版本,旧版本中也包括 DB_ROLL_PTR 列、事务 ID 列。 因此,undo log 中记录的所有版本会形成一个链表,值越旧,它就在链中越靠近末尾的位置,该记录的最新值,记录在聚簇索引中的记录上。

我们借用《MySQL 是怎样运行的:从根儿上理解 MySQL》中的一幅图,加深下对版本链的理解。

图中所示的是插入、并删除一条记录后,形成的版本链。

在读未提交事务隔离级别下,直接读取记录的最新版本即可; 在串行级别下,需要使用锁来保证。 所以,版本链主要应用于读提交和可重复读事务隔离级别。 它要解决的核心问题就是,版本链中的哪些版本是当前事务可见的。

### Mysql中binlogundolog和redolog日志功能及区别 #### binlog(二进制日志) binlogMySQL Server 层的日志,记录了数据库的逻辑操作,例如 `INSERT`、`UPDATE` 和 `DELETE` 等语句。它以逻辑日志的形式存储,记录的是 SQL 语句的原始逻辑[^1]。 - **功能**: - 主要用于主从复制,从库通过读取主库的 binlog 来实现数据同步[^2]。 - 可用于数据恢复,当数据库发生故障时,可以通过重放 binlog 来恢复数据[^4]。 - **刷盘时机**: - `sync_binlog` 参数控制 binlog 的刷盘行为,建议设置为 1,即每次事务提交时都将 binlog 写入磁盘,以保证异常重启后日志不会丢失[^4]。 - **格式**: - 支持三种格式:`STATEMENT`、`ROW` 和 `MIXED`。`ROW` 格式记录每一行的变化,而 `STATEMENT` 格式记录执行的 SQL 语句。 --- #### redo log(重做日志) redo log 是 InnoDB 存储引擎层的日志,记录了数据页的物理变化,属于物理日志[^3]。 - **功能**: - 主要用于崩溃恢复。当数据库因意外宕机或介质故障而需要恢复时,redo log 能够确保未写入磁盘的数据能够被重新应用,从而保证数据的一致性和完整性。 - **记录形式**: - 记录的是数据页的物理变化,而非具体的 SQL 操作。例如,某一行数据从 A 值更新为 B 值,redo log 会记录这一变化的详细信息[^2]。 - **写入方式**: - redo log 是循环写入的,其日志空间大小是固定的。当一个日志文件写满后,会切换到下一个日志文件,形成循环使用。 --- #### undo log(回滚日志undo log 同样是 InnoDB 存储引擎层的日志,主要用于事务的回滚以及多版本并发控制(MVCC)。 - **功能**: - 在事务执行过程中,如果需要回滚,undo log 提供了撤销操作的能力。例如,某条记录被更新后,undo log 会保存该记录的旧版本,以便在需要时恢复旧值[^2]。 - 支持 MVCC,允许多个事务同时读取不同版本的数据,而不会相互干扰[^1]。 - **记录形式**: - 记录的是事务执行前的数据状态,与 redo log 不同,undo log 是逻辑日志[^2]。 --- #### 区别总结 | 日志类型 | 层级 | 功能 | 记录形式 | 使用场景 | |------------|--------------|--------------------------|----------------|------------------------------------| | binlog | MySQL Server | 数据恢复、主从复制 | 逻辑日志 | 数据同步、故障恢复 | | redo log | InnoDB 引擎 | 崩溃恢复 | 物理日志 | 数据库异常宕机后的数据一致性恢复 | | undo log | InnoDB 引擎 | 事务回滚、MVCC | 逻辑日志 | 回滚操作、支持并发读 | --- ### 示例代码:查看 binlog 文件内容 ```bash # 查看当前 binlog 文件列表 SHOW MASTER LOGS; # 查看指定 binlog 文件的内容 mysqlbinlog /var/lib/mysql/binlog.000001; ``` --- ###
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值