问题

MySQL事务ACID实现原理?

答案

核心概况

MySQL InnoDB通过Undo Log、Redo Log、锁机制、MVCC等技术组合实现了事务的ACID特性。

特性 实现机制 核心组件
原子性(Atomicity) Undo Log 回滚日志
一致性(Consistency) Undo Log + Redo Log + 约束 日志系统 + 数据库约束
隔离性(Isolation) MVCC + 锁机制 ReadView + Lock
持久性(Durability) Redo Log 重做日志 + 刷盘策略

1. 原子性(Atomicity)

实现原理

通过Undo Log(回滚日志)实现,记录事务修改前的数据,用于事务回滚。

Undo Log工作机制

记录变更前的数据

-- 事务开始
START TRANSACTION;

-- UPDATE操作
UPDATE user SET age = 30 WHERE id = 1;

Undo Log记录内容

Undo Log记录:
- 操作类型:UPDATE
- 表:user
- 主键:id=1
- 旧值:age=20
- 新值:age=30
- 事务ID:100
- 回滚指针:指向上一个Undo Log

回滚过程

-- 如果事务需要回滚
ROLLBACK;
回滚执行:
1. 读取Undo Log
2. 根据Undo Log反向操作:UPDATE user SET age = 20 WHERE id = 1
3. 恢复到事务开始前的状态
4. 标记Undo Log可清理

INSERT、UPDATE、DELETE的Undo Log

INSERT的Undo Log

INSERT INTO user VALUES (10, 'Tom', 25);

-- Undo Log:记录主键
-- 回滚时执行:DELETE FROM user WHERE id = 10;

UPDATE的Undo Log

UPDATE user SET age = 30 WHERE id = 1;

-- Undo Log:记录旧值
-- 回滚时执行:UPDATE user SET age = 20 WHERE id = 1;

DELETE的Undo Log

DELETE FROM user WHERE id = 1;

-- Undo Log:记录完整的旧记录
-- 回滚时执行:INSERT INTO user VALUES (1, 'Tom', 20);

2. 持久性(Durability)

实现原理

通过Redo Log(重做日志)实现,记录事务修改后的数据,用于崩溃恢复。

Redo Log工作机制

WAL(Write-Ahead Logging)策略

数据修改流程:
1. 修改内存中的Buffer Pool数据
2. 写Redo Log到内存的Log Buffer
3. 写Redo Log到磁盘(根据innodb_flush_log_at_trx_commit配置)
4. 事务提交
5. 后台异步刷脏页到磁盘

为什么需要Redo Log?

问题:如果直接写数据页到磁盘:

  • 数据页很大(默认16KB)
  • 随机IO,性能差
  • 事务提交慢

解决:先写Redo Log:

  • Redo Log是顺序写,性能高
  • Redo Log记录小,只记录修改内容
  • 后台异步刷数据页

Redo Log记录内容

UPDATE user SET age = 30 WHERE id = 1;
Redo Log记录:
- 表空间ID:1
- 页号:100
- 偏移量:200
- 修改内容:将age字段从20改为30
- 事务ID:100
- LSN(日志序列号):12345

崩溃恢复流程

MySQL崩溃后重启:
1. 读取Redo Log
2. 找到未刷盘的已提交事务
3. 重做这些事务的修改(redo)
4. 找到未提交的事务
5. 回滚这些事务(使用Undo Log)
6. 恢复完成

innodb_flush_log_at_trx_commit参数

# 参数配置

# 0:每秒刷一次磁盘(性能最高,可能丢1秒数据)
innodb_flush_log_at_trx_commit = 0

# 1:每次事务提交都刷磁盘(默认,最安全)
innodb_flush_log_at_trx_commit = 1

# 2:每次提交写OS缓存,每秒刷磁盘(折中方案)
innodb_flush_log_at_trx_commit = 2
行为 安全性 性能
0 每秒刷盘 低(可能丢1秒数据)
1 每次提交刷盘 高(不丢数据)
2 写OS缓存,每秒刷盘 中(MySQL崩溃不丢,OS崩溃可能丢1秒)

3. 隔离性(Isolation)

实现原理

通过MVCC(多版本并发控制)+ 锁机制实现。

MVCC实现快照读隔离

核心组件

  1. 隐藏字段
    • DB_TRX_ID:事务ID
    • DB_ROLL_PTR:回滚指针
  2. Undo Log版本链
    最新版本 → 历史版本1 → 历史版本2 → NULL
    
  3. ReadView
    ReadView {
        m_ids: [活跃事务ID列表],
        min_trx_id: 最小活跃事务ID,
        max_trx_id: 下一个事务ID,
        creator_trx_id: 创建者事务ID
    }
    

可见性判断

boolean isVisible(long trx_id, ReadView view) {
    // 自己的修改可见
    if (trx_id == view.creator_trx_id) return true;

    // 早已提交的可见
    if (trx_id < view.min_trx_id) return true;

    // 还未开启的不可见
    if (trx_id >= view.max_trx_id) return false;

    // 在范围内,看是否在活跃列表
    return !view.m_ids.contains(trx_id);
}

锁机制实现当前读隔离

锁的类型

  1. 表级锁
    • 表锁(Table Lock)
    • 意向锁(Intention Lock)
  2. 行级锁
    • 记录锁(Record Lock):锁定单条记录
    • 间隙锁(Gap Lock):锁定记录间隙
    • Next-Key Lock:记录锁+间隙锁

当前读加锁

-- 共享锁(S锁)
SELECT * FROM user WHERE id = 1 LOCK IN SHARE MODE;

-- 排他锁(X锁)
SELECT * FROM user WHERE id = 1 FOR UPDATE;
UPDATE user SET age = 30 WHERE id = 1;
DELETE FROM user WHERE id = 1;

RC vs RR隔离级别实现差异

隔离级别 ReadView生成时机 加锁方式
RC 每次查询生成新的 只有记录锁
RR 事务第一次查询时生成,后续复用 记录锁+间隙锁(Next-Key Lock)

4. 一致性(Consistency)

实现原理

一致性是事务的最终目标,通过原子性、隔离性、持久性共同保证。

实现机制

1. 数据库约束

-- 主键约束
CREATE TABLE user (
    id INT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    age INT CHECK (age > 0),
    UNIQUE KEY uk_name (name)
);

2. 原子性保证

-- 转账场景
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;
COMMIT;

-- 如果任一步骤失败,通过Undo Log回滚,保证数据一致

3. 隔离性保证

-- 通过MVCC和锁机制,防止并发事务之间的干扰
START TRANSACTION;
SELECT balance FROM account WHERE id = 1 FOR UPDATE;
-- 锁定记录,防止其他事务修改
UPDATE account SET balance = balance - 100 WHERE id = 1;
COMMIT;

4. 持久性保证

-- 事务提交后,通过Redo Log保证数据不丢失
COMMIT;
-- Redo Log刷盘,即使崩溃也能恢复

5. ACID协同工作流程

完整事务执行流程

-- 事务:转账100元
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;
COMMIT;

详细执行步骤

1. 开始事务(获取事务ID=100)

2. 执行第一条UPDATE
   2.1 加X锁(id=1)
   2.2 记录Undo Log(旧值balance=1000)
   2.3 修改Buffer Pool中的数据(balance=900)
   2.4 记录Redo Log(新值balance=900)

3. 执行第二条UPDATE
   3.1 加X锁(id=2)
   3.2 记录Undo Log(旧值balance=500)
   3.3 修改Buffer Pool中的数据(balance=600)
   3.4 记录Redo Log(新值balance=600)

4. COMMIT提交
   4.1 写入Commit记录到Redo Log
   4.2 根据innodb_flush_log_at_trx_commit刷Redo Log
   4.3 释放锁
   4.4 标记Undo Log可清理

5. 后台异步刷脏页到磁盘

崩溃恢复时:
- 如果崩溃在步骤4之前:通过Undo Log回滚(原子性)
- 如果崩溃在步骤4之后:通过Redo Log重做(持久性)

6. 关键日志系统总结

Undo Log vs Redo Log

对比项 Undo Log Redo Log
作用 回滚事务 崩溃恢复
保证特性 原子性 持久性
记录内容 修改前的数据(旧值) 修改后的数据(新值)
写入时机 数据修改前 数据修改后
物理/逻辑 逻辑日志(SQL级别) 物理日志(页级别)
存储位置 系统表空间/独立表空间 ib_logfile0, ib_logfile1
用途 ROLLBACK、MVCC COMMIT后崩溃恢复

面试要点总结

  1. 原子性:Undo Log记录旧值,失败时回滚
  2. 持久性:Redo Log记录新值,崩溃后重做
  3. 隔离性:MVCC(快照读)+ 锁机制(当前读)
  4. 一致性:由其他三个特性 + 数据库约束共同保证

  5. 关键流程
    • 修改前:写Undo Log
    • 修改后:写Redo Log
    • 提交时:刷Redo Log
    • 崩溃时:Redo重做 + Undo回滚
  6. 重要参数
    • innodb_flush_log_at_trx_commit:控制Redo Log刷盘策略
    • transaction_isolation:控制隔离级别
  7. 理解ACID需要掌握
    • Undo Log和Redo Log的区别
    • MVCC和锁机制的配合
    • WAL(Write-Ahead Logging)原理
    • 崩溃恢复流程