InnoDB 执行 UPDATE 时的完整操作

第一步:① 加载缓存数据(从硬盘到 BufferPool)

执行器 收到 UPDATE 语句后,首先告诉 InnoDB:"把 id=1002 这条数据所在的数据页加载到内存里"

复制代码
硬盘(.ibd文件)  ──①──►  BufferPool(缓存页)
   数据页                  缓存页

为什么要这样设计?

如果没有 BufferPool,直接读写硬盘会怎样?

  • 硬盘是机械结构(或 SSD),一次随机 IO 需要 毫秒级 时间

  • 内存访问是 纳秒级,相差百万倍

  • 如果每次 UPDATE 都直接改硬盘,数据库性能会低到无法使用

所以设计了 BufferPool: 把硬盘上的"数据页"(默认 16KB)整体加载到内存的"缓存页"中,后续的读写都在内存里操作,大幅提升性能。


第二步:② 写入数据的旧值(到 UndoLog)

数据页加载到 BufferPool 后,InnoDB 先把这条数据的旧值写入 UndoLog

复制代码
BufferPool(旧数据)  ──②──►  硬盘(UndoLog)

为什么要这样设计?

如果没有 UndoLog,会发生什么?

假设你执行了 UPDATE name='小明',然后事务还没提交,另一个事务来查询这条数据------它应该看到旧值还是新值?

  • 需要看到旧值(因为事务还没提交,新数据是"脏"的)

  • 也需要能回滚(如果事务最终 ROLLBACK,必须恢复到修改前的状态)

UndoLog 就是用来存"修改前的数据快照"的。 它让 InnoDB 支持:

  1. 事务回滚:ROLLBACK 时,用 UndoLog 把数据恢复回去

  2. MVCC(多版本并发控制):其他事务读取时,可以通过 UndoLog 链找到旧版本数据


第三步:③ 更新内存数据(BufferPool 中的缓存页)

现在,执行器真正修改 BufferPool 中缓存页的数据:

复制代码
执行器  ──③──►  BufferPool(缓存页)
              name='小明'  ← 新值写入内存

注意:此时硬盘上的 .ibd 文件还没有变

BufferPool 里的缓存页变成了"脏页"(内存和硬盘不一致)。这个脏页不会立刻写回硬盘。


为什么要延迟写回硬盘?

  • UPDATE 可能只改了 100 字节,但数据页有 16KB,写回硬盘是"整页写"

  • 如果每次 UPDATE 都立刻刷盘,会产生大量随机 IO,性能极差

  • 脏页由后台线程异步刷盘(Checkpoint 机制),把随机 IO 合并成顺序 IO

代价: 如果此时数据库崩溃,内存里的脏页会丢失。那数据不就丢了吗?

这就是下面 RedoLog 要解决的问题。


第四步:④ 写 RedoLog Buffer

执行器把这次修改操作记录到 RedoLog Buffer(内存中的日志缓冲区):

复制代码
执行器  ──④──►  RedoLog Buffer(内存)
              记录了:"在某某页某某偏移处,把 name 改成了 '小明'"

为什么要这样设计?

RedoLog 记录的是"物理修改"(哪个页、哪个位置、改成了什么),而不是 SQL 语句本身。

如果没有 RedoLog,会发生什么?

  • 脏页还没刷盘,数据库突然崩溃 → 内存数据丢失 → 这次 UPDATE 永久丢失

  • 有了 RedoLog,崩溃重启后,InnoDB 可以重放 RedoLog,把 BufferPool 恢复到崩溃前的状态


RedoLog 是"物理日志",UndoLog 是"逻辑日志":

  • RedoLog:记录"怎么把数据改回来"(用于崩溃恢复)

  • UndoLog:记录"怎么把数据改回去"(用于回滚和 MVCC)


第五步:⑤ Commit 刷 RedoLog 到硬盘

事务准备提交时,RedoLog Buffer 中的内容要通过 fsync() 强制刷到硬盘的 RedoLog 文件

复制代码
RedoLog Buffer  ──⑤──►  硬盘(RedoLog文件)
              fsync() 强制刷盘

这里有个关键参数:innodb_flush_log_at_trx_commit

图中标注了三种模式:

含义 安全性 性能
0 延迟写 最低(每秒才刷盘,崩溃可能丢1秒数据) 最高
1 实时写,实时刷(默认) 最高(每次事务提交都 fsync 最低
2 实时写,延迟刷 中等(写到 OS 的 PageCache,每秒 fsync 中等

为什么默认是 1?

因为数据库的核心承诺是 ACID 中的 D(Durability,持久性)。如果事务提交了,数据就必须永久保存。所以默认每次提交都 fsync 到硬盘。


第六步:⑥ 准备提交事务(写 binlog)

RedoLog 刷盘后,还要写 binlog

复制代码
执行器  ──⑥──►  PageCache  ──fsync()──►  硬盘(binlog文件)

为什么要同时有 RedoLog 和 binlog?它们不是重复了吗?

不是重复的,它们的设计目的完全不同:

RedoLog binlog
所属 InnoDB 引擎层 MySQL Server 层
用途 崩溃恢复 主从复制、数据恢复
格式 物理日志(页级别) 逻辑日志(SQL 语句)
写入方式 循环写(固定大小,会覆盖) 追加写(不会覆盖,一直增长)

如果没有 binlog,会发生什么?

  • 主库崩溃了,RedoLog 能恢复主库数据

  • 但从库怎么同步?RedoLog 是 InnoDB 私有的,MySQL Server 层看不到

  • binlog 是 Server 层的,所有存储引擎通用,主从复制必须靠 binlog


第七步:⑦ 写入 binlog 名称和 commit 标记(到 RedoLog)

最后一步,InnoDB 在 RedoLog 中写入一个特殊的标记:"本次事务对应的 binlog 文件名和位置"

复制代码
RedoLog  ◄──⑦──  写入 binlog 名称 + commit 标记

为什么要这样设计?(两阶段提交)

这是整个流程中最精妙的部分,叫做 "两阶段提交"

如果没有这个标记,会发生什么?

假设事务提交过程中,数据库崩溃了:

崩溃时机 RedoLog 状态 binlog 状态 后果
RedoLog 刷盘后,binlog 刷盘前 有记录 无记录 主库有数据,从库没有 → 主从不一致
binlog 刷盘后,RedoLog 写 commit 标记前 无 commit 标记 有记录 重启后 RedoLog 认为事务未完成,回滚 → 主库无数据,从库有 → 主从不一致

两阶段提交解决了这个问题:

  1. Prepare 阶段:RedoLog 写入,状态设为 prepare

  2. Commit 阶段:binlog 写入后,RedoLog 写入 commit 标记

崩溃恢复时,InnoDB 检查:

  • 如果 RedoLog 有 prepare,但没有 commit 标记,且 binlog 也没有 → 回滚

  • 如果 RedoLog 有 prepare,且有 commit 标记,且 binlog 也有 → 提交

  • 这样就保证了 RedoLog 和 binlog 要么都有,要么都没有,永远不会不一致


完整流程总结图

复制代码
客户端发送 UPDATE 语句
        │
        ▼
┌───────────────┐
│ ① 加载数据页  │  从 .ibd 文件加载到 BufferPool
│   到 BufferPool│
└───────┬───────┘
        ▼
┌───────────────┐
│ ② 写 UndoLog  │  记录旧值,用于回滚和 MVCC
│   (硬盘)      │
└───────┬───────┘
        ▼
┌───────────────┐
│ ③ 更新内存数据 │  修改 BufferPool 中的缓存页(脏页)
│   (BufferPool)│
└───────┬───────┘
        ▼
┌───────────────┐
│ ④ 写 RedoLog  │  记录物理修改到 RedoLog Buffer
│   Buffer      │
└───────┬───────┘
        ▼
┌───────────────┐
│ ⑤ fsync 刷    │  RedoLog Buffer → 硬盘 RedoLog 文件
│   RedoLog     │  (innodb_flush_log_at_trx_commit 控制)
└───────┬───────┘
        ▼
┌───────────────┐
│ ⑥ 写 binlog   │  记录逻辑 SQL 到 binlog(主从复制用)
│   (fsync)     │  (sync_binlog 控制)
└───────┬───────┘
        ▼
┌───────────────┐
│ ⑦ RedoLog 写  │  写入 commit 标记,完成两阶段提交
│   commit 标记 │
└───────────────┘
        │
        ▼
    事务提交成功

一句话总结

BufferPool 让读写变快,UndoLog 让回滚和并发查询成为可能,RedoLog 让崩溃后不丢数据,binlog 让主从复制能工作,两阶段提交让 RedoLog 和 binlog 永远保持一致。

这四者配合,才支撑起了 InnoDB 的高性能、事务安全和数据一致性。

相关推荐
c2385610 小时前
MySQL 基础用法(上):库表管理与数据增删改
c语言·数据库·c++·mysql
码农颜14 小时前
5.2.2 隔离级别
数据库·mysql
骇客野人14 小时前
MySQL 信创化迁移至 PolarDB-X 整体实施方案
数据库·mysql
molaoye19 小时前
win10下安装MySQL 8.0.*实录
数据库·mysql
番茄炒鸡蛋加糖20 小时前
中级核心技术1--MySQL/Java 并发
java·数据库·mysql
灯澜忆梦21 小时前
【MySQL10】进阶篇 | 索引_#1
数据库·sql·mysql
OceanWaves199321 小时前
mysql 5.7.29 主从同步配置
数据库·mysql·adb
molaoye21 小时前
win10下切换MySQL版本
数据库·mysql
c238561 天前
MySQL 基础用法(下):查询进阶与核心特性
android·c语言·c++·mysql