Administrator
Published on 2025-03-10 / 7 Visits
0
0

MySQL 是如何实现事务的?

MySQL 的事务实现主要依赖于存储引擎(默认使用 InnoDB),通过 日志机制锁机制多版本并发控制(MVCC) 来满足事务的 ACID 特性。以下是其核心实现原理:


一、ACID 特性的实现

1. 原子性(Atomicity)

  • 目标:事务的所有操作要么全部完成,要么全部回滚。
  • 实现
    • Undo Log(回滚日志)
      • 记录事务修改前的数据快照(旧版本数据)。
      • 若事务回滚或崩溃恢复时,通过 Undo Log 将数据恢复到事务前的状态。
      • Undo Log 存储在 Undo Tablespace 中(默认位于系统表空间或独立文件)。
    • 事务状态管理
      • 每个事务有唯一的事务 ID(TRX_ID),通过 InnoDB 的 事务系统 跟踪事务状态(活跃、提交、回滚)。

2. 持久性(Durability)

  • 目标:事务提交后,修改永久保存,即使系统崩溃也不丢失。
  • 实现
    • Redo Log(重做日志)
      • 记录事务对数据页的物理修改(例如:“将 page 5 的 offset 100 处修改为 42”)。
      • 事务提交时,Redo Log 会先刷盘(fsync),保证数据修改的持久性(即使宕机,恢复时也能重放日志)。
    • Double Write Buffer
      • 防止数据页部分写入(partial page write)导致的损坏。在修改数据页前,先将页副本写入 Double Write Buffer,再写入实际位置。

3. 隔离性(Isolation)

  • 目标:并发事务之间互不干扰。
  • 实现
    • 锁机制
      • 共享锁(S Lock):允许读,阻止其他事务加排他锁。
      • 排他锁(X Lock):阻止其他事务加任何锁。
      • 行级锁:InnoDB 默认对修改的行加锁(而非表锁),减少锁冲突。
      • 间隙锁(Gap Lock)Next-Key Lock:防止幻读(Phantom Read)。
    • MVCC(多版本并发控制)
      • 通过 Undo Log 维护数据的多个版本,实现非锁定读(Read Committed 和 Repeatable Read 隔离级别下)。
      • 每个事务启动时生成一个 ReadView,决定可见哪些版本的数据:
        • TRX_ID < 当前活跃事务的最小 ID → 可见。
        • TRX_ID > 当前事务 ID → 不可见。
        • TRX_ID 在活跃事务列表中 → 不可见。

4. 一致性(Consistency)

  • 目标:事务执行后,数据库从一个有效状态转换到另一个有效状态。
  • 实现
    • 通过原子性、隔离性、持久性共同保证。
    • 数据库约束(如主键、外键、唯一约束、CHECK 约束)确保数据逻辑一致。

二、事务执行流程(以 InnoDB 为例)

1. 事务开启

  • 显式:BEGINSTART TRANSACTION
  • 隐式:执行第一条 SQL 时自动开启。

2. 数据修改(以 UPDATE 为例)

  1. 加锁
    • 对目标行加排他锁(X Lock),阻止其他事务修改。
  2. 写 Undo Log
    • 记录修改前的数据到 Undo Log,用于回滚和 MVCC。
  3. 修改 Buffer Pool
    • 在内存中更新数据页(此时数据页变为“脏页”)。
  4. 写 Redo Log
    • 将修改记录到 Redo Log Buffer(事务提交时刷盘)。

3. 事务提交

  1. Redo Log 刷盘
    • 将 Redo Log Buffer 中的日志写入磁盘(fsync),确保持久性。
  2. 释放锁
    • 释放事务持有的行锁(间隙锁可能保留到事务结束)。
  3. 清理 Undo Log
    • 若事务已提交,Undo Log 可被后续事务覆盖(若其他事务不再需要旧版本数据)。

4. 事务回滚

  1. 逆向应用 Undo Log
    • 根据 Undo Log 将数据恢复到修改前的状态。
  2. 释放锁
    • 释放所有锁资源。

三、隔离级别与并发控制

MySQL 支持四种隔离级别(由低到高):

  1. 读未提交(Read Uncommitted)
    • 可能读到未提交的数据(脏读),无锁或 MVCC 控制。
  2. 读已提交(Read Committed)
    • 通过 MVCC 实现:每次查询生成新 ReadView,避免脏读。
  3. 可重复读(Repeatable Read)
    • 默认隔离级别。事务开始时生成 ReadView,后续查询复用该视图,避免不可重复读。
    • 通过 Next-Key Lock(行锁 + 间隙锁)防止幻读。
  4. 串行化(Serializable)
    • 所有读操作加共享锁,写操作加排他锁,完全串行执行。

四、崩溃恢复

InnoDB 重启时通过以下步骤恢复事务:

  1. Redo Log 重放
    • 根据 Redo Log 恢复未刷盘的脏页(确保已提交事务的持久性)。
  2. Undo Log 回滚
    • 对未提交的事务(TRX_ID 在活跃事务列表中)应用 Undo Log 回滚。

五、关键机制对比

机制作用关联 ACID
Undo Log回滚事务、实现 MVCC原子性、隔离性
Redo Log保证数据修改的持久性持久性
锁机制控制并发访问,防止脏写、不可重复读隔离性
MVCC实现非锁定读,避免读写冲突隔离性
DoubleWrite防止数据页部分写入损坏持久性

六、示例:MVCC 如何避免脏读

假设事务 A 更新一行数据:

-- 事务 A
BEGIN;
UPDATE users SET balance = 100 WHERE id = 1;  -- 旧值 balance=50
  • Undo Log 会记录 balance=50
  • 事务 B 在 Read Committed 隔离级别下读取该行:
    -- 事务 B
    BEGIN;
    SELECT balance FROM users WHERE id = 1;  -- 返回 50(ReadView 看不到事务 A 的未提交修改)
    
  • 事务 A 提交后,事务 B 再次查询会生成新 ReadView,看到 balance=100

总结

MySQL(InnoDB)通过 Redo LogUndo Log 实现原子性与持久性,通过 锁机制MVCC 实现隔离性,最终达成一致性。设计时需根据业务场景选择合适的隔离级别,并关注锁竞争与日志写入性能。


Comment