Skip to content

第 3 讲:事务、ACID、redo log、undo log、binlog

核心结论(10 条必记)

  1. 事务是一组要么全成功、要么全失败的操作
  2. ACID 是事务的四大特性 -- 原子性、一致性、隔离性、持久性
  3. 原子性主要依赖 undo log
  4. 持久性主要依赖 redo log
  5. binlog 主要用于主从复制和恢复
  6. redo log 是引擎层物理日志 -- InnoDB 特有
  7. binlog 是 Server 层逻辑日志 -- 所有引擎通用
  8. MySQL 用 WAL(Write-Ahead Logging)提高写性能 -- 先写日志再刷数据页
  9. 两阶段提交保证 redo log 和 binlog 一致
  10. 大事务是线上高危问题,要尽量避免

一、事务是什么

一组操作,要么全部成功,要么全部失败。保证数据从一个一致状态变到另一个一致状态。

sql
-- 转账示例
BEGIN;
UPDATE account SET balance = balance - 100 WHERE user_id = A;
UPDATE account SET balance = balance + 100 WHERE user_id = B;
COMMIT;  -- 成功则提交
-- ROLLBACK;  -- 失败则回滚

二、ACID 四大特性

特性含义举例
Atomicity 原子性不可再分,要么全成功要么全失败转账两步必须一起成功
Consistency 一致性事务前后数据保持一致转账前后 A+B 总金额不变
Isolation 隔离性并发事务互不干扰同时扣库存不会超卖
Durability 持久性提交后永久生效,宕机不丢支付成功后状态不会变回

一致性是最终目标,原子性/隔离性/持久性都是为了帮助实现一致性


三、MySQL 整体架构:Server 层 vs 引擎层

+----------------------------------+
|          Server 层               |
|  连接管理 / SQL解析 / 优化器     |
|  执行器 / binlog                 |
+----------------------------------+
|        存储引擎层 (InnoDB)       |
|  数据存储 / 索引 / 事务 / 锁    |
|  Buffer Pool / redo log / undo log|
+----------------------------------+
  • Server 层:接收 SQL、理解 SQL、决定怎么执行
  • InnoDB 引擎层:真正存取数据、管事务、管锁、管索引、管日志

四、Buffer Pool -- 内存缓存区

InnoDB 用来缓存数据页和索引页的内存区域,用于减少磁盘 IO

为什么需要?

磁盘慢、内存快 -> 把热点数据缓存到内存

工作方式

  • 数据按管理,通常 16KB 一页
  • 读:先查 Buffer Pool,没有再从磁盘加载
  • 写:先在 Buffer Pool 中修改,记录 redo log,后台线程择机刷盘

更新流程

1. 数据页加载到 Buffer Pool
2. 在内存中修改
3. 记录 redo log
4. 后台线程择机刷盘(不是立刻)

Buffer Pool 大小和命中率直接影响性能


五、三种日志对比

维度undo logredo logbinlog
所属层InnoDB 引擎层InnoDB 引擎层Server 层
日志类型逻辑日志物理日志逻辑日志
记录内容修改前的旧值某页做了什么修改执行了什么变更操作
核心作用回滚 + MVCC 历史版本崩溃恢复主从复制 + 增量恢复
写入方式事务中持续写循环写追加写
生活类比"后悔药""施工记录""业务流水账"

undo log -- 后悔药

修改前记录旧值,失败时按它回滚。同时也是 MVCC 的历史版本仓库。

redo log -- 施工记录

记录已做但可能还没落盘的修改。宕机后按它重建。核心思路:先写日志再刷数据页(WAL)

binlog -- 业务流水账

记录所有变更操作。从库按它同步数据,也可按时间点恢复误删数据。


六、为什么不能直接刷数据页?

数据库页 16KB,每次改一行都把整页刷盘太慢。

解决:WAL(Write-Ahead Logging)

先写日志(redo log) -> 再慢慢刷数据页
  • 性能好(日志是顺序写,数据页是随机写)
  • 安全(日志落盘后,宕机能恢复)

七、事务提交流程

sql
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 1;
COMMIT;
第1步: 写 undo log      -> 记录旧值,保证能回滚
第2步: 修改 Buffer Pool  -> 数据页在内存中修改
第3步: 写 redo log      -> 保证宕机可恢复
第4步: 写 binlog        -> 保证复制和恢复
第5步: 提交成功          -> 关键日志落盘后才算真正提交

八、两阶段提交(2PC)

为什么需要?

redo log 和 binlog 分属不同层,如果写入时机不一致:

  • 主库数据和 binlog 不一致
  • 主从复制出问题
  • 恢复出问题

流程

1. prepare 阶段  -> redo log 写入并标记 prepare
2. 写 binlog     -> binlog 落盘
3. commit 阶段   -> redo log 标记 commit

即使中间宕机,也能根据 redo log 状态判断事务是否真正提交


九、大事务的危害

危害原因
锁持有时间长事务没提交,锁不释放,阻塞其他事务
undo log 膨胀事务越大,回滚代价越高,版本链更长
redo log 压力大短时间大量修改,日志刷盘压力大
主从复制延迟binlog 很大,从库回放变慢
回滚成本高失败后回滚时间很长

典型错误

sql
begin;
update order_info set status = 2 where create_time < '2024-01-01';  -- 几十万行
commit;

正确做法

分批处理 / 小事务提交 / 控制事务持续时间


十、自动提交机制

MySQL 默认 autocommit = 1,每条 SQL 是一个独立事务。

需要显式事务的场景:转账、下单+扣库存+写支付流水、批量一致性更新


十一、MVCC -- 多版本并发控制

通过数据的多个历史版本,让普通读在并发下也能读到一致数据,读不阻塞写,写不阻塞普通读

核心原理

一条记录不是只有当前值,而是保留历史版本链。不同事务根据可见性规则看到不同版本。

依赖组件

  • undo log -- 提供历史版本
  • 隐藏字段 -- 记录事务信息
  • Read View -- 决定可见性

undo log 与 MVCC 的关系

undo log 既是"回滚工具",也是"历史版本仓库"

数据更新时,旧值通过 undo log 串成版本链,事务读数据时沿版本链找自己能看到的版本。


十二、主从复制

架构

应用 -> 主库(写)
应用 -> 从库(读)
  • 主库:负责写操作
  • 从库:复制主库数据,提供读服务

复制流程(依赖 binlog)

1. 主库执行写操作 -> 记录到 binlog
2. 从库拉取主库的 binlog
3. 从库重放这些操作 -> 数据保持一致

读写分离

大多数业务读远多于写,把读分散到从库减轻主库压力。

主从延迟

主库刚写完,从库还没来得及拉取/执行 -> 主从数据暂时不一致

典型问题:刚下单写入主库,马上从从库查可能查不到。


十三、两种"恢复"的区别

redo log 恢复binlog 恢复
场景宕机后已提交数据不丢误删/误更新后的历史恢复
方式重启后根据 redo log 补回修改先恢复备份 + 按 binlog 重放到误操作前
类型引擎级崩溃恢复业务级增量恢复

十四、一条更新 SQL 的完整链路

sql
update user set age = 20 where id = 1;
Server 层:
  解析 SQL -> 选择执行计划 -> 调用 InnoDB

InnoDB 层:
  找到数据页 -> 加载到 Buffer Pool
  -> 写 undo log(可回滚)
  -> 在 Buffer Pool 中修改
  -> 写 redo log(可崩溃恢复)

Server 层:
  写 binlog(给从库复制 + 恢复用)

两阶段提交保证 redo log 和 binlog 一致

十五、概念速记口诀

Buffer Pool 是内存缓存区;InnoDB 是真正存数据的引擎;undo log 负责回滚和历史版本;MVCC 负责并发读一致性;redo log 负责宕机恢复;binlog 负责主从复制和增量恢复;主从复制就是主库写、从库跟着抄。


十六、面试高频题

1. ACID 是什么?

原子性(全成功/全失败) -> 一致性(前后数据一致) -> 隔离性(并发互不干扰) -> 持久性(提交后不丢)

2. undo log 和 redo log 区别?

undo log 记录修改前旧值 -> 用于回滚和 MVCC redo log 记录修改后物理变更 -> 用于崩溃恢复,保证持久性

3. redo log 和 binlog 区别?

redo log: InnoDB 引擎层 / 物理日志 / 循环写 / 崩溃恢复 binlog: Server 层 / 逻辑日志 / 追加写 / 主从复制和恢复

4. 为什么需要两阶段提交?

redo log 和 binlog 分属不同层 -> 写入时机不一致会导致数据和复制日志不一致 -> 两阶段提交保证两者一致


练习题(待完成)

  • [ ] 练习 1:转账场景为什么必须用事务?不用事务最坏会怎样?
  • [ ] 练习 2:用一句话概括 undo log / redo log / binlog 各自作用
  • [ ] 练习 3:为什么事务提交成功后 MySQL 崩溃数据不丢?
  • [ ] 练习 4:为什么 MySQL 不能只要 redo log 不要 binlog?

基于 VitePress 构建