mysql mvcc

在 MySQL 的 InnoDB 引擎中,MVCC (Multi-Version Concurrency Control,多版本并发控制) 是实现高性能并发读写的核心机制。

简单来说,它的作用是让“读不加锁,读写不冲突”。在处理事务时,它能让你在不影响别人写数据的情况下,读取到某个时间点的历史快照。


1. MVCC 的核心组件

MVCC 的实现并不是靠单一技术,而是由隐藏列Undo LogRead View 三者协作完成的。

① 记录中的隐藏列

InnoDB 会在每行数据的后面偷偷增加几个隐藏字段:

  • DB_TRX_ID (6字节):最后一次插入或更新该行的事务 ID
  • DB_ROLL_PTR (7字节)回滚指针,指向该行上一个版本的 Undo Log 记录。
  • DB_ROW_ID:如果表没有主键,InnoDB 会自动生成这个隐藏的聚簇索引 ID。

② Undo Log (回滚日志)

当你修改一条记录时,InnoDB 会先把原始数据拷贝一份到 Undo Log 里。通过 DB_ROLL_PTR 指针,这些不同版本的记录会被串联成一条版本链

③ Read View (一致性视图)

这是 MVCC 的“大脑”。当一个事务执行快照读(Simple Select)时,会产生一个 Read View,里面记录了:

  • m_ids:当前系统中活跃(还没提交)的事务 ID 列表。
  • min_trx_id:活跃事务中最小的 ID。
  • max_trx_id:系统即将分配给下一个事务的 ID(当前最大 ID + 1)。
  • creator_trx_id:创建这个 Read View 的事务 ID。

2. 数据可见性算法(重点)

当事务想读某行数据时,会拿该行的 DB_TRX_ID 与 Read View 进行比对,判断规则如下:

  1. TRX_ID == creator_trx_id:说明这行是你自己改的,可见
  2. TRX_ID < min_trx_id:说明改这行数据的事务在快照创建前就提交了,可见
  3. TRX_ID >= max_trx_id:说明改这行的事务在快照创建后才开启,不可见
  4. min_trx_id <= TRX_ID < max_trx_id
    • 如果 TRX_ID 在 m_ids 列表里:说明事务还没提交,不可见
    • 如果不在:说明事务已提交,可见

如果不可见怎么办? 顺着 DB_ROLL_PTR 去 Undo Log 找上一个版本,重复上述判断,直到找到可见的版本。


3. 不同隔离级别下的表现

MVCC 主要服务于 RC (已提交读)RR (可重复读)

  • RC (Read Committed)
    • 每次 Select 都会重新生成一个新的 Read View。
    • 结果:能看到别人刚提交的修改,所以会产生“不可重复读”。
  • RR (Repeatable Read)
    • 只有第一次 Select 时生成 Read View,后续查询复用同一个。
    • 结果:整个事务期间看到的数据一致,从原理上解决了“不可重复读”,并在很大程度上避免了“幻读”。

4. 总结:为什么需要 MVCC?

如果没有 MVCC,数据库为了保证隔离性必须频繁加锁。

  • 传统方案:读写互相阻塞,并发性能差。
  • MVCC 方案
    • 快照读 (Snapshot Read):普通的 SELECT,不加锁,读历史版本。
    • 当前读 (Current Read)SELECT ... FOR UPDATEUPDATE/DELETE,读取最新版本并加锁。

一句话总结:MVCC 通过保存数据的历史版本,让数据库在处理大量并发读写时,依然能保持像“单机快照”一样的丝滑体验。

mysql 日志

在 MySQL 的体系结构中,redo log 保证持久性,undo log 保证原子性,binlog 用于数据同步和恢复。


1. 三种日志对比概览

特性 Redo Log (重做日志) Undo Log (回滚日志) Binlog (归档日志)
所属层级 InnoDB 存储引擎层 InnoDB 存储引擎层 MySQL Server 层
记录内容 物理日志(在哪个页做了什么修改) 逻辑日志(相反的操作,如插入对应删除) 逻辑日志(原始 SQL 或数据行变动)
文件格式 循环写入,空间固定 存储在回滚段(Rollback Segment) 追加写入,不覆盖旧文件
主要作用 Crash-safe(宕机恢复) 事务回滚MVCC 主从复制数据恢复

2. 深入解析

① Redo Log:保证“不丢数据”

当有一条记录需要更新时,InnoDB 会先更新内存(Buffer Pool),并把这次修改记录到 Redo Log 中。此时更新就算完成了。

  • WAL 技术 (Write-Ahead Logging):先写日志,再写磁盘。
  • Checkpoint:Redo Log 大小固定(如一组 4 个文件),当日志写满时,系统会停下来将 Buffer Pool 中的脏页刷入磁盘,然后擦除旧日志。
  • 意义:即使数据库异常重启,InnoDB 也可以根据 Redo Log 将未落盘的数据恢复。

② Undo Log:数据的“后悔药”

Undo Log 记录了事务执行过程中的中间状态。

  • 回滚操作:如果你执行了 INSERT,Undo Log 记录对应的 DELETE;如果你执行了 UPDATE,它记录修改前的值。
  • MVCC 实现:当一个事务读取某行记录,但该记录正被其他事务修改时,它可以通过 Undo Log 链找到该行之前的版本(快照读)。

③ Binlog:数据库的“流水账”

这是 MySQL Server 层产生的日志,记录了所有的 DDL 和 DML 操作(不包含查询)。

  • 主从复制:从库读取主库的 Binlog 并重放,实现数据同步。
  • 数据恢复:结合全量备份,重放 Binlog 可以将数据库恢复到任意时间点。

3. 三日志如何协作?(二阶段提交)

为了保证 Redo Log 和 Binlog 的一致性(防止 Redo Log 成功但 Binlog 没记上的情况),MySQL 使用了 二阶段提交 (Two-Phase Commit)

  1. Prepare 阶段:InnoDB 将更新记录写入 Redo Log,并将其状态设置为 prepare
  2. 写 Binlog:MySQL Server 将操作写入 Binlog 并刷盘。
  3. Commit 阶段:MySQL Server 调用引擎提交接口,将 Redo Log 状态改为 commit

为什么这么麻烦?
如果没有二阶段提交,可能会出现:Redo Log 记录了修改(物理数据恢复了),但 Binlog 没记录(从库没同步),导致主从数据不一致。


总结

  • Redo Log 是为了 Crash-safe,保证数据库挂了重启能恢复。
  • Undo Log 是为了 事务回滚MVCC
  • Binlog 是为了 主从同步数据恢复

理解底层实现,本质上是理解 数据如何存放(磁盘)数据如何流转(内存) 以及 一致性如何保证(日志)


1. 磁盘结构:数据的“物理家园”

MySQL 的数据并不是散乱存放的,而是按照严格的层级组织在 .ibd 文件中:

  • 表空间 (Tablespace):逻辑上的最高层。
  • 段 (Segment):分为数据段(叶子节点)、索引段(非叶子节点)等。
  • 区 (Extent):固定大小为 1MB(连续的 64 个页)。
  • 页 (Page)最小的磁盘管理单位,默认 16KB
    • 底层原理:B+ 树的每个节点就是一个“页”。为了减少磁盘 I/O,MySQL 尽量让相关数据在物理上靠近。

2. 内存结构:性能的“加速器”

为了弥补磁盘和 CPU 之间的巨大速度鸿沟,InnoDB 设计了复杂的内存管理机制(In-Memory Structures):

Buffer Pool (缓冲池)

这是 MySQL 内存中最大的一块,缓存了数据页和索引页。

  • LRU 算法优化:为了防止全表扫描把热数据刷掉,InnoDB 将 LRU 链表分为 New SublistOld Sublist。新读取的页先放到 Old 区域,只有多次访问且满足时间间隔后才升入 New 区域。
  • 刷脏 (Flush):后台线程会异步将 Buffer Pool 中的脏页写入磁盘。

Change Buffer (写缓冲区)

  • 原理:如果你修改的数据页不在内存里,InnoDB 不会立刻去磁盘读,而是先把修改操作记在 Change Buffer 中。等下次读取该页时再进行 Merge。
  • 限制:仅适用于非唯一二级索引(因为唯一索引需要去磁盘校验唯一性)。

3. 核心机制:并发与可靠性的博弈

B+ 树索引底层

为什么是 B+ 树而不是 B 树或红黑树?

  1. 更矮更胖:阶数更多,磁盘 I/O 次数极少(通常 3-4 层即可支撑千万级数据)。
  2. 叶子节点链表:所有数据都在叶子节点,且通过双向链表连接,极大地优化了范围查询

ARIES 恢复算法与二阶段提交

MySQL 结合了我们之前聊过的日志系统,实现了分布式事务中的 2PC (Two-Phase Commit)

  • 原子性 (A):靠 Undo Log 实现回滚。
  • 一致性 (C):由约束(如主键、外键)和代码逻辑保证。
  • 隔离性 (I):由 锁 (Locks)MVCC 共同实现。
  • 持久性 (D):靠 Redo Log 确保掉电不丢失。

4. 线程模型

MySQL 是单进程多线程模型:

  • Master Thread:负责异步刷脏、合并 Change Buffer 等。
  • IO Thread:负责处理 AIO 请求的回调。
  • Purge Thread:负责回收已经不再需要的 Undo Log 记录,防止日志文件无限膨胀。

研究建议

深入研究底层实现时,我建议你按这个顺序“突破”:

  1. 看 Page 结构:理解行溢出、行格式(Compact/Dynamic)。
  2. 看 2PC 流程:理解为什么 Redo Log 和 Binlog 要协同工作。
  3. 看 锁机制:理解 Next-Key Lock 如何在 RR 级别下缓解幻读。

Buffer Pool (缓冲池)和双写缓冲区

在 MySQL 的底层架构中,Buffer Pool (缓冲池) 是为了提升性能(减少磁盘 I/O),而 Doublewrite Buffer (双写缓冲区) 则是为了保证数据的安全性(防止页断裂)。

它们一个负责“快”,一个负责“稳”。


1. Buffer Pool:内存的核心

Buffer Pool 是 InnoDB 在内存中开辟的一块连续空间,用来缓存磁盘上的数据页。

核心管理机制

由于内存空间有限,InnoDB 必须高效地管理这些页:

  • Free List (空闲链表):记录哪些内存页是干净的,可以直接用来存放磁盘读入的数据。
  • LRU List (最近最少使用链表)
    • 优化:InnoDB 将其分为 New Sublist (5/8)Old Sublist (3/8)
    • 目的:防止全表扫描(Table Scan)一次性将所有热数据挤出内存。新读入的页先放在 Old Header,只有满足一定时间间隔后再次被访问,才会升入 New 区域。
  • Flush List (脏页链表):记录哪些页在内存中被修改了(变成了“脏页”),等待后台线程异步刷入磁盘。

2. Doublewrite Buffer:防止“页断裂”

这是很多初学者容易忽略但极其重要的机制。

为什么需要它?(Partial Page Write 问题)

  • 背景:MySQL 的数据页大小通常是 16KB,而操作系统的文件系统(如 Linux 的 ext4)最小写入单位通常是 4KB
  • 危机:当 InnoDB 正在向磁盘写入一个 16KB 的页时,如果系统突然断电,可能只写了前 4KB 或 8KB。
  • 结果:这个页在磁盘上损坏了(Partial Page Write)。
  • Redo Log 能救吗? 不能。 Redo Log 记录的是物理偏移量的修改(比如:在偏移量 100 处改成了 'A')。如果基础页(Page)本身由于“半写”损坏了,Redo Log 无法在损坏的页上进行重做。

双写缓冲的工作流程

为了解决这个问题,InnoDB 在将脏页刷入 .ibd 数据文件之前,先走一步“中转站”:

  1. 第一步:将脏页复制到内存中的 doublewrite buffer
  2. 第二步:将 doublewrite buffer 分两次、每次 1MB 顺次写入磁盘上的共享表空间(物理上的连续存储块)。
  3. 第三步:在确认双写缓冲区写入成功后,再将脏页写入各个表对应的 .ibd 文件(离散写入)。

恢复逻辑

  • 如果写入 .ibd 时崩溃,但双写缓冲区里有完整的备份,MySQL 重启时会用双写缓冲里的页覆盖损坏的页,再应用 Redo Log。
  • 如果写入双写缓冲区时崩溃,由于原有的 .ibd 文件还是完整的旧版本,直接应用 Redo Log 恢复即可。

3. 两者的关系与对比

特性 Buffer Pool Doublewrite Buffer
位置 纯内存 内存 + 磁盘 (系统表空间)
目标 提高读写性能,减少磁盘访问 保证写入的原子性,防止页损坏
副作用 占用大量内存 带来一定的双倍磁盘写开销(约 5-10%)
是否可关闭 必须开启 可以关闭(但在不支持原子写的系统上极度危险)

进阶思考

随着硬件的发展,现在一些高级文件系统(如 ZFS)或者某些高性能企业级 SSD 已经支持了 Atomic Write (原子写)。在这些环境下,由于硬件能保证 16KB 写入的完整性,我们可以通过设置 innodb_doublewrite = 0 来关闭双写缓冲区,从而压榨出更多的性能。

双写缓冲区如何保证不丢失数据

双写缓冲区(Doublewrite Buffer)的核心逻辑在于:它为“脏页落盘”这个高风险动作提供了一个物理备份。

即便发生极端情况(如断电),导致正在写入的数据页“断裂”损坏,MySQL 依然能从双写缓冲区中找回一份完整的拷贝。


1. 它是如何保证数据不丢失的?

要理解它的原理,必须先看 “页断裂(Partial Page Write)” 这一致命伤。

场景还原:如果没有双写缓冲区

  1. InnoDB 准备将一个 16KB 的脏页写入磁盘。
  2. 磁盘 IO 写入了前 4KB,此时突然断电。
  3. 结果:磁盘上的页处于中间状态(半新半旧),由于校验和(Checksum)对不上,这个页被判定为物理损坏
  4. Redo Log 为什么救不了? Redo Log 记录的是物理偏移量的增量修改(例如:在第 10 字节处将 A 改为 B)。它要求基础页必须是完整的。如果页本身物理结构坏了,Redo Log 就像在废纸上画画,根本无法恢复。

解决方案:先备份,再落盘

有了双写缓冲区,过程变为:

  1. 内存备份:脏页先在内存中拷贝到双写缓冲区。
  2. 顺序写备份(安全区):双写缓冲区将数据分两次、批量地写入磁盘上的共享表空间(连续存储块)。由于是顺序 IO,性能开销较小。
  3. 离散写原表(目标区):确认备份写入磁盘成功后,再将脏页写入各自对应的 .ibd 文件。

故障恢复逻辑:

  • 如果在第 2 步崩溃:由于 .ibd 原始文件还没动,数据还是旧但完整的。重启后,直接通过 Redo Log 恢复即可。
  • 如果在第 3 步崩溃:此时 .ibd 文件页损坏了。但没关系,MySQL 重启时发现页损坏,会去双写缓冲区磁盘空间找对应的完整副本,覆盖掉损坏的页,然后再应用 Redo Log

2. 双写缓冲区有多大?

双写缓冲区的结构分为内存层磁盘层

磁盘上的物理空间

  • 默认大小:通常为 2MB
  • 结构:它由 2 个区(Extent) 组成,每个区 1MB。
  • 位置:在 MySQL 8.0 之前,它固定在系统表空间(ibdata1)中;在 MySQL 8.0 之后,它有了专门的 .dblwr 文件,甚至可以配置多个文件来减少竞争。

内存中的缓冲区

  • 大小:同样是 2MB
  • 分块:它由 128 个页(Page) 组成(每个页 16KB,\(128 \times 16KB = 2048KB = 2MB\))。

3. 性能与代价

既然每次写数据都要写两次磁盘,性能会下降吗?

  • 性能损耗:大约在 5% - 10% 左右。
  • 为什么损耗不大? 因为双写缓冲区的磁盘写入是顺序 IO(Sequential IO),速度极快;而写入 .ibd 的过程是随机 IO(Random IO)。两者的开销不在一个量级上。

什么时候可以关掉它?

如果你满足以下条件之一,可以设置 innodb_doublewrite = 0 来压榨性能:

  1. 文件系统支持原子写:例如某些高端 NVMe 推进器或特定的文件系统(如 ZFS),能保证 16KB 写入的原子性。
  2. 对数据一致性要求不高:比如作为临时缓存的数据。
  3. 从库节点:如果主库有双写,从库为了追求同步速度,在某些架构下会选择关闭。

总结一下:
双写缓冲区就像是写正式文件前的底稿。万一正式文件写一半笔没墨了(断电),我们还能照着底稿(双写缓冲)再抄一份。


1. 为什么“第1步挂了”不可怕?

在第1步(内存拷贝和准备写入磁盘备份)发生崩溃时,情况如下:

  • 内存状态:内存中的脏页还在(虽然断电会消失),但关键在于磁盘上的原始数据页(.ibd文件)完全没动过
  • Redo Log 状态:在修改 Buffer Pool 里的数据之前,Redo Log 已经先落盘了(WAL原则)。

恢复逻辑:

当 MySQL 重启进行崩溃恢复(Crash Recovery)时:

  1. 检测磁盘上的数据页,发现它们的 LSN(日志序列号)落后于 Redo Log。
  2. 因为磁盘上的页是完整且干净的旧版本(没有页断裂),MySQL 直接读取这些旧页。
  3. 通过 Redo Log 里的物理记录,把缺失的修改“重做”一遍。
  4. 结论:此时根本不需要双写缓冲区参与,Redo Log 就能搞定一切。

2. 核心原理:双写缓冲区到底是救谁的?

双写缓冲区存在的唯一目的,是救治“正在写磁盘却没写完”导致的物理损坏。

我们可以把落盘过程看作三个风险阶段:

阶段 动作 如果此时崩溃 恢复方案
阶段 1 内存拷贝/准备写备份 磁盘原文件完好 直接用 Redo Log 恢复
阶段 2 正在写双写磁盘备份 磁盘原文件完好 直接用 Redo Log 恢复(损坏的备份直接丢弃)
阶段 3 正在写 .ibd 目标文件 磁盘原文件页断裂(坏了) 用双写备份覆盖坏页,再跑 Redo Log

关键点:只有在“写目标文件”的时候挂了,导致原数据页物理上变“碎”了,Redo Log 才会失效。而第 1 步和第 2 步,原文件都是安全的,所以完全不虚。


3. 深度思考:那双写缓冲区自己写坏了怎么办?

如果是在第 2 步(向共享表空间的双写备份区写入)时断电了,导致双写缓冲区里的备份页是断裂的呢?

这也没关系!
MySQL 重启时会检查双写缓冲区里的页。如果发现备份页的 Checksum(校验和)不对,说明备份是坏的。但由于此时 .ibd 原始文件还没开始写,它是完好无损的旧版,MySQL 依然会选择跳过双写缓冲区,直接用 Redo Log 恢复。

总结

双写缓冲区是一种“后备方案”

  • 只有当“主力(磁盘原页)”伤得体无完肤(页断裂)时,它才上场救火。
  • 如果只是“准备阶段”“后备方案自己”伤了,只要“主力”还在,Redo Log 就能包治百病。

redo log 何时写入

WAL(Write-Ahead Logging,预写日志)机制**。

简单直接的回答是:Redo Log 的写入一定是在双写缓冲区(Doublewrite Buffer)落盘之前完成的。 如果把数据页落盘比作“搬家”,Redo Log 就是你的“搬家清单”。你一定是先写好清单,才开始搬家具。


1. 完整的写入时序图

为了保证数据绝对安全,InnoDB 遵循严格的操作顺序:

  1. 修改内存:事务修改 Buffer Pool 中的页,该页变为“脏页”。
  2. 生成 Redo Log:同时产生一条 Redo Log 记录,先存入内存中的 Redo Log Buffer
  3. Redo Log 刷盘:根据策略(通常是事务提交时),将 Redo Log 写入磁盘。此时,事务已经可以返回成功了。
  4. 准备刷脏:后台线程决定将脏页写入磁盘。
  5. 双写缓冲(第一步):脏页先拷贝到内存的 Doublewrite Buffer
  6. 双写缓冲(第二步):Doublewrite Buffer 批量、顺序写入磁盘。
  7. 最终落盘:最后,脏页才写入对应的 .ibd 数据文件。

2. Redo Log 是如何写入的?

Redo Log 的写入过程并不是简单的“写文件”,它为了极致的性能设计了三层结构:

第一层:Redo Log Buffer(内存)

在修改数据时,日志先写到内存缓冲区。它是顺序写的,非常快。

第二层:OS Cache(操作系统缓存)

通过调用系统的 write() 函数,日志从 MySQL 进程转交到了操作系统的文件缓存中。此时如果 MySQL 挂了,数据还在内存里;如果操作系统挂了,数据就丢了。

第三层:Log File(磁盘)

通过调用 fsync(),强制操作系统将数据刷入物理磁盘。这是真正的“落地”。

控制写入时机的参数:innodb_flush_log_at_trx_commit

你可以通过这个参数控制 Redo Log 的写入强度:

  • 0:每秒写入并刷盘一次(性能最高,但崩溃可能丢 1 秒数据)。
  • 1(默认)每次事务提交都必须刷盘(最安全,保证 ACID)。
  • 2:每次事务提交写到 OS Cache,每秒刷盘一次(折中方案)。

3. 为什么一定要先写 Redo Log?

这就是所谓的 WAL 原则(Write-Ahead Logging)。

如果先写双写缓冲区(即开始刷脏页),万一写到一半断电了,磁盘上的页坏了,而此时 Redo Log 还没写完,那这个数据就彻底没救了。

现在的逻辑是:

  • 只要 Redo Log 落地了,即使后面的双写缓冲区、数据页落盘全部失败,MySQL 重启时也能根据 Redo Log 把内存里的修改重新“复现”出来。
  • Redo Log 记录的是增量修改,双写缓冲区保护的是物理页完整。 它们相辅相成,但 Redo Log 永远走在最前面。

总结

Redo Log 和双写缓冲区的写入是异步的。

  • Redo Log 随着事务提交频繁写入。
  • 双写缓冲区 随着脏页刷新(Checkpoint 机制)批量写入。

当你观察磁盘 IO 时,你会发现 Redo Log 的写入通常是极小的、持续的 IO,而双写缓冲和数据落盘则是大块的、周期性的 IO。


生成redo log时,服务挂了会丢数据吗,redo log写入时undo log何时写入

这是一个非常精细的边界问题,触及了事务原子性(Atomicity)和持久性(Durability)的底线。

我们先说结论:只要事务没提交(Commit),Redo Log 没写完也不算“丢数据”;只要事务提交成功,数据就绝对不会丢。


1. 生成 Redo Log 时服务挂了,会丢数据吗?

这取决于你的事务进行到了哪一步。我们要分两种情况看:

情况 A:事务还在运行中,尚未执行 COMMIT

  • 现状:你执行了一半的 SQL,生成了一部分 Redo Log 在内存(Redo Log Buffer)里,还没来得及写磁盘,服务挂了。
  • 结果不丢数据。因为在数据库看来,这个事务从未成功过
  • 恢复:重启后,磁盘上的数据还是旧的,内存里的残余日志丢了就丢了,数据库保持一致性。

情况 B:你发起了 COMMIT 命令,但 Redo Log 写入磁盘时挂了

  • 现状:MySQL 正在把这个事务的日志从内存往磁盘刷。
  • 结果事务失败。客户端会收到一个报错或连接断开。
  • 判定标准:只有当 Redo Log 完整写入磁盘(对于默认配置 innodb_flush_log_at_trx_commit = 1 而言),MySQL 才会告诉客户端“提交成功”。
  • 结论只要你收到了“提交成功”的反馈,数据就一定在磁盘日志里,不会丢。 如果没收到成功反馈,这个事务就被视为失败,重启后会被回滚。

2. Redo Log 写入时,Undo Log 何时写入?

这是一个经典的误区:很多人以为先写完 Undo 再写 Redo,或者反过来。
真相是:Undo Log 的产生是伴随着数据修改同时发生的,而且 Undo Log 本身的写入也受 Redo Log 保护。

写入时序(微观视角):

当你执行一条 UPDATE 语句时,InnoDB 内部的顺序是:

  1. 准备 Undo:在内存中找一个 Undo 页面,写入撤销当前修改所需的信息。
  2. 记录 Undo 的 Redo(关键点) 产生一条 Redo Log,记录“我刚才对 Undo 页面做了什么修改”。
  3. 修改数据页:修改 Buffer Pool 里的数据页。
  4. 记录数据的 Redo:产生一条 Redo Log,记录“我刚才对数据页面做了什么修改”。

结论:Undo Log 比数据更早产生,但它们的“改变记录”都会被写进 Redo Log 并在事务提交时一起刷盘。


3. 为什么要这样设计?(底层逻辑)

你可能会问:为什么 Undo Log 也要记 Redo Log?

  • 原因:Undo Log 也是存在磁盘文件里的(物理页)。为了保证性能,修改 Undo 页时也是先改内存。
  • 场景:如果修改了数据,也生成了 Undo,但没记 Redo 就断电了。重启后,数据页可能坏了,你连 Undo 页都找不回来,怎么回滚?
  • 解决:把 Undo Log 的修改也当成一种“数据修改”,交给 Redo Log 去保证持久性。

总结:

  1. Redo Log 没写盘,事务就不算成功。只要事务没成功,就不存在“丢数据”的说法,因为状态没变。
  2. Undo Log 先于数据修改产生
  3. Redo Log 包含了 Undo Log 的变动记录。在磁盘层面,Redo Log 是老大,它保护了数据页,也保护了 Undo 页。