MySQL 事务的实现原理详解

MySQL 事务的实现原理详解

重点围绕 InnoDB 存储引擎,因为真正支持事务的是 InnoDB。


一、事务与 ACID 简单回顾

事务(Transaction):一组要么全部成功、要么全部失败的 SQL 操作,是数据库并发控制的基本单位。

ACID 四个特性:

  1. 原子性(Atomicity)

    • 要么全部执行,要么全部回滚。
    • 失败时,数据库能恢复到事务开始前的状态。
  2. 一致性(Consistency)

    • 事务前后,数据库从一个“合法状态”到另一个“合法状态”。
    • 约束(主键、唯一约束、外键等)不会被破坏。
  3. 隔离性(Isolation)

    • 多个事务并发执行时,相互之间的可见性如何控制。
    • 对应四种隔离级别:RU、RC、RR、Serializable。
  4. 持久性(Durability)

    • 提交后的修改即使宕机也不会丢失。
    • 靠的是各种日志和刷盘机制。

二、InnoDB 事务的大框架

InnoDB 为了实现 ACID,核心用到了这几样东西:

  1. Redo Log(重做日志) —— 保证持久性
  2. Undo Log(回滚日志) —— 保证原子性 + MVCC 的基础
  3. 锁(Lock)系统 —— 行级锁实现隔离性的一部分
  4. MVCC(多版本并发控制) —— 实现 RC / RR 隔离级别下的“快照读”
  5. 两阶段提交(Redo Log + Binlog) —— 保证崩溃恢复 & 主从复制的一致性

整体可以理解为:

“写操作先记日志(WAL 思想),通过日志 + 版本控制 + 锁来维持 ACID。”


三、原子性:Undo Log 如何工作

原子性 = 要么全做,要么全不做。InnoDB 通过 Undo Log 实现“回滚能力”。

1. Undo Log 是什么

  • Undo Log 记录的是:如何把新数据恢复成旧数据
  • 例如:UPDATE t SET age = 18 WHERE id = 1;
    • Undo 里会记录:原来的 age 是多少。

2. Undo Log 的作用

  1. 事务回滚

    • 事务失败或主动 ROLLBACK 时,InnoDB 按 Undo Log 逆向操作,把数据恢复成修改前的版本。
  2. MVCC 的版本链基础

    • 每行记录除了当前值外,还通过 Undo Log 把历史版本串成一条“版本链”。
    • 这样“快照读”就可以读到旧版本,做到“读到事务开始时的视图”。

3. Undo Log 的类型

  • Insert Undo Log:对 INSERT 操作产生。

    • 事务未提交前,需要它来回滚“插入”。
    • 提交后这类 Undo 其实可以被丢弃(因为没人再需要看到“插入之前”的“没有这条记录”的版本)。
  • Update Undo Log:包括 UPDATEDELETE

    • 既用于回滚,也用于给其他事务提供历史版本(MVCC)。
    • 提交后不能立刻删除,要等没有事务再需要这些旧版本。

四、持久性:Redo Log 与 WAL 思想

持久性 = 提交之后不能丢

1. Redo Log 是什么

  • Redo 记录的是:对“数据页”的物理修改,类似于“操作步骤”。
  • 比如:修改了哪一个表空间、哪一页、哪个偏移量、改成什么值。

2. 为什么需要 Redo Log

直接把页面写回磁盘成本很高:

  • 随机写多、IO 慢;
  • 如果宕机在一半,页面处于中间状态。

所以 InnoDB 采用 WAL(Write-Ahead Logging)

  1. 先写 Redo Log(顺序写) 到日志文件/缓冲。
  2. 再异步把数据页刷回磁盘。

只要 Redo Log 写成功,就认为事务“持久化”了。

3. 提交时发生了什么

一个写事务大概流程:

  1. 修改内存中的 Buffer Pool 页(脏页)。
  2. 生成 Redo Log 写入 Redo Log Buffer。
  3. 事务 COMMIT 时,必须保证相关 Redo 至少刷新到磁盘(或者根据 innodb_flush_log_at_trx_commit 的策略)。
  4. 真正的数据页晚点刷也没关系,宕机时可以靠 Redo Log“重做”回来。

4. 崩溃恢复时 Redo 的作用

  • 启动时:扫描 Redo Log,从最后一次一致性检查点开始重做,确保所有已经提交的事务都物理落盘。

五、隔离性:锁 + MVCC 的组合拳

隔离性主要通过两部分实现:

  1. 锁机制(Locking) —— 控制对“同一行或范围”的并发修改
  2. MVCC(多版本并发控制) —— 让读操作尽量不用加锁,直接读“快照”

1. 锁的种类(InnoDB 行级锁)

常见锁:

  • 记录锁(Record Lock):锁住某一条记录。
  • 间隙锁(Gap Lock):锁住两个值之间的“间隙”,不锁已有记录。
  • Next-Key Lock:记录锁 + 间隙锁,锁住“某条记录以及它前面的间隙”,用于防止幻读。
  • 意向锁(Intention Lock):表级锁,用来快速判断“某个表上是否有人加过行锁”。

思路:在高并发下,通过尽可能细粒度的行锁 + 间隙锁控制写冲突。

2. MVCC 的基本结构

InnoDB 每行记录内部有几个隐藏字段:

  • trx_id:最近一次修改该行的事务 id。
  • roll_pointer:指向 Undo Log 中该行的前一个版本。

于是:

  • 当前行 = 最新版本。
  • 沿着 roll_pointer 可以找到历史版本。
  • 不同事务按照自己的“快照规则”,去选择某个版本来读。

这样:

  • 快照读(普通 SELECT):不加锁,通过版本链读取旧数据版本。
  • 当前读(SELECT … FOR UPDATE / UPDATE / DELETE):要加锁,读的是当前最新版本。

六、四种隔离级别是如何实现的

MySQL 提供 4 种隔离级别:

  1. 读未提交(READ UNCOMMITTED,RU)
  2. 读已提交(READ COMMITTED,RC)
  3. 可重复读(REPEATABLE READ,RR)—— InnoDB 默认
  4. 串行化(SERIALIZABLE)

对应现象:脏读、不可重复读、幻读。

1. READ UNCOMMITTED

  • 读操作可以看到“未提交事务”的修改。
  • 实现上基本不使用 MVCC 快照,直接读最新版本。
  • 问题多,一般不用。

2. READ COMMITTED

  • 每条 SQL 开始时生成一个新的“快照”。
  • 同一个事务内,不同的 SELECT 可能读到不同版本的数据(不可重复读)。
  • 实现上:
    • 通过 MVCC,按照当前语句执行时的活跃事务列表过滤版本。

3. REPEATABLE READ(InnoDB 默认)

  • 事务开始时生成一致性视图(快照)。
  • 整个事务生命周期内,快照不变:
    • 多次 SELECT 结果一样(可重复读)。
  • 幻读问题:
    • 对于快照读,通过 MVCC 的版本规则避免看到其他事务新插入的数据。
    • 对于当前读(加锁的范围查询),通过 Next-Key Lock(记录锁 + 间隙锁)防止“幻影记录”插入。

4. SERIALIZABLE

  • 所有读都是加锁读,相当于“串行执行”。
  • 并发性最差,但隔离性最高。

七、一致性:规则 + 日志 + 约束

一致性是结果上的“正确性”,主要来源于:

  1. 应用层逻辑:业务本身必须写对。
  2. 数据库约束:主键、外键、唯一约束、触发器等。
  3. 事务隔离:防止并发行为破坏规则。
  4. 日志与恢复机制:崩溃后回放 Redo / 回滚 Undo,确保不会得到“半条更新”的中间状态。

简化理解:

原子性 + 隔离性 + 持久性 + 约束 = 一致性。


八、Redo Log 与 Binlog 的两阶段提交

为了保证 MySQL Server 层的 BinlogInnoDB 的 Redo Log 一致,InnoDB 采用“两阶段提交”:

  1. 阶段一:准备阶段(Prepare)

    • 写入 Redo Log(状态标记为 prepare)。
  2. 阶段二:提交阶段(Commit)

    • 写 Binlog。
    • 把 Redo Log 状态改为 commit

崩溃恢复时:

  • 看到 Redo Log 处于 prepare 状态,判断 Binlog 是否完整,决定是提交还是回滚。

这样就保证:

  • 不会出现 binlog 里有记录但 InnoDB 没有提交,或者反过来的情况。
  • 主从复制、崩溃恢复结果一致。

九、锁与两阶段锁协议

InnoDB 行锁遵循 两阶段锁协议(2PL)

  1. 在执行 SQL 过程中,不断 加锁,直到事务结束。
  2. 事务提交或回滚时,一次性释放所有锁

这会导致一些现象:

  • 一条后面的 SQL 所加的锁,可能会让前面已经执行的 SQL 也被锁住资源(因为都属于一个事务)。
  • 死锁:多个事务互相等待对方的锁。
    • InnoDB 会检测死锁(构建 wait-for 图),选出代价最小的事务回滚。

十、崩溃恢复的完整过程(简化版)

假设实例宕机,再启动:

  1. 加载数据字典 & 基本信息
  2. 扫描 Redo Log,从 checkpoint 开始:
    • 对于已经 commit 的事务但数据没落盘的,进行 重做
  3. 回滚未提交事务
    • 利用 Undo Log,把它们做过的修改挨个撤销。

最终:

  • 所有已提交的事务都“看起来像是完全执行完了”;
  • 所有未提交的事务“像是从未发生过”。
  • 原子性 + 持久性 得到了保证。

十一、整体总结(帮助你脑中形成一张图)

可以把 MySQL(InnoDB)的事务实现想象成一套“流水线”:

  1. 写前日志(WAL)
    • 修改先写 Redo Log,再写数据页,保证崩溃可恢复(持久性)。
  2. 回滚日志(Undo Log)
    • 记录旧版本,支持回滚(原子性)+ 快照读(MVCC)。
  3. MVCC + 锁
    • 快照读用 MVCC,当前读用行锁 + 间隙锁,共同实现隔离性。
  4. 两阶段提交
    • Redo Log + Binlog 的两阶段提交,确保存储引擎和 Server 层的一致性。
  5. 约束 + 规则
    • 加上主键、唯一约束、外键以及业务逻辑,保证数据语义上的一致性。

掌握上面这些,你就已经从“会用事务”升级到“能从实现原理思考问题”的阶段了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值