事务四大特性

面试官问:"MySQL 事务的 ACID 是什么?每个特性分别是怎么实现的?"

小王说:"A 是原子性,I 是隔离性,C 是一致性,D 是持久性。原子性就是一起成功或一起失败。"

面试官追问:"那 InnoDB 是怎么保证原子性的?"

小王说:"...用 redo log?"

面试官继续追问:"redo log 不是保证持久性的吗?"

小王开始混乱了。

ACID 是 MySQL 事务的灵魂。但这四个字背后对应的具体实现机制,很多人搞不清楚。原子性、隔离性、持久性各自有各自的实现方式,redo log 和 undo log 各司其职。这道题能答清楚的候选人,对 MySQL 的理解已经到了一定深度。

一、ACID 四大特性 🔴

1.1 ACID 定义

特性定义MySQL 实现
Atomicity(原子性)事务是最小执行单位,要么全部成功,要么全部失败undo log
Consistency(一致性)事务执行前后,数据库状态保持一致依赖原子性、隔离性、持久性
Isolation(隔离性)并发执行的事务互不干扰锁机制 + MVCC
Durability(持久性)事务提交后,数据永久保存redo log + 刷盘策略

1.2 深入理解一致性

一致性是最容易误解的概念。很多人以为一致性是数据库保证的,但实际上:

一致性 = 业务层面的约束 + 数据库的保证

-- 转账场景:A 账户 1000 元,B 账户 1000 元
-- A 向 B 转账 500 元

-- 原子性:A 扣 500 和 B 加 500 要么都成功,要么都失败
-- 隔离性:并发转账时不会导致余额计算错误
-- 持久性:提交后即使数据库崩溃,余额也不能变

-- 以上三个特性保证了:
-- 1000 + 1000 = 1500 + 500 = 2000(一致性)

数据库本身无法理解"转账前后总金额不变"这种业务规则,但数据库通过 ACID 机制保证了技术层面的一致性。业务层面的一致性需要应用程序来保证。

1.3 ❌ 错误示范

候选人原话:"ACID 就是事务的四个特性,InnoDB 支持,MyISAM 不支持。"

问题诊断

  • 只知道概念,不理解每个特性的实现机制
  • 把 ACID 当成一个整体,没有区分各自的技术实现
  • 混淆了 redo log 和 undo log 的作用

候选人原话 2:"redo log 保证原子性,undo log 保证持久性。"

问题诊断:完全搞反了 redo log 和 undo log 的职责。

【面试官心理】 这道题我能从多个角度追问。基础问法是"ACID 是什么",进阶问法是"每个特性怎么实现",深入问法是"redo log 和 undo log 分别在什么时候写、什么时候刷盘"。能答出两阶段提交的,是真正理解 InnoDB 事务机制的候选人。

二、原子性:undo log 🔴

2.1 undo log 的作用

undo log 记录了数据修改前的值,用于事务回滚和 MVCC。

-- 事务开始
BEGIN;

-- 执行更新
UPDATE users SET balance = balance - 500 WHERE id = 1;  -- A 账户扣 500

-- undo log 记录:
-- 用户 ID=1,balance 从 1000 改成了 500,undo log 记录旧值 1000

UPDATE users SET balance = balance + 500 WHERE id = 2;  -- B 账户加 500

-- 此时如果回滚
ROLLBACK;

-- InnoDB 读取 undo log:
-- 用户 ID=1,balance 恢复到 1000
-- 用户 ID=2,balance 恢复到 1000

2.2 undo log 的结构

InnoDB 的 undo log 存储在系统表空间或独立的 undo 表空间中。每个事务有自己独立的 undo 页面链表。

undo log 链:
┌──────────────────────────────────────┐
│ UPDATE users SET balance=500        │  ← 最新操作
│   WHERE id=1;                        │
│   old_value: balance=1000            │
└──────────────────────────────────────┘

┌──────────────────────────────────────┐
│ INSERT INTO orders (...)             │  ← 更早的操作
│   row_id: 10086                      │
└──────────────────────────────────────┘

2.3 undo log 的类型

类型作用回滚方式
INSERT undo log记录新插入的行DELETE 该行
UPDATE undo log记录更新前的值UPDATE 回旧值
DELETE undo log记录删除前的值INSERT 回该行

三、持久性:redo log 🔴

3.1 为什么需要 redo log?

问题:事务提交后,数据需要持久化到磁盘。但如果每条 UPDATE 都直接刷盘,性能极差(随机 IO)。

解决:引入 redo log。先把操作记录到 redo log(顺序写),然后异步刷数据到磁盘。

刷盘策略:
1. 用户执行 UPDATE
2. 数据写入 Buffer Pool(内存)
3. 操作记录写入 redo log buffer(内存)
4. 事务提交时,调用 fsync 将 redo log 刷到磁盘
5. 后台线程异步将 Buffer Pool 中的脏页刷到磁盘

3.2 redo log 的刷盘策略

-- 查看 innodb_flush_log_at_trx_commit 配置
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';

-- 配置值:
-- 1(默认):事务提交时刷 redo log 到磁盘,最安全
-- 0:每秒刷一次,可能丢失最多 1 秒的数据
-- 2:刷到操作系统缓存,不保证落盘,最快但不安全
⚠️

innodb_flush_log_at_trx_commit = 1 是 MySQL 默认也是最安全的刷盘策略。设置为 0 或 2 会提升写入性能,但牺牲持久性。在生产环境中,如果对数据可靠性有要求,不要改这个配置。

3.3 redo log 的结构

InnoDB 的 redo log 是循环写入的:

┌─────────────────────────────────────────────────────────────┐
│                    redo log buffer                           │
│   ┌─────────┬─────────┬─────────┬─────────┬─────────┐      │
│   │ log1    │ log2    │ log3    │ log4    │ log5    │      │
│   └────┬────┴────┬────┴────┬────┴────┬────┴────┬────┘      │
└────────┼─────────┼─────────┼─────────┼─────────┼───────────┘
         ↓         ↓         ↓         ↓         ↓
┌─────────────────────────────────────────────────────────────┐
│                    redo log file                            │
│   ┌─────────┬─────────┬─────────┬─────────┬─────────┐        │
│   │ log1    │ log2    │ log3    │ log4    │ log5    │  (循环) │
│   └─────────┴─────────┴─────────┴─────────┴─────────┘        │
└─────────────────────────────────────────────────────────────┘

redo log 文件是固定大小的(默认 48MB x 2)。写满后循环覆盖,所以 redo log 只能保存最近一段时间的操作。这也是 redo log 不适合做主从复制的原因(binlog 才是做主从复制的日志)。

四、隔离性:锁 + MVCC 🟡

4.1 隔离性的实现机制

InnoDB 通过锁机制MVCC(多版本并发控制) 两套机制来实现隔离性。

  • 锁机制:悲观并发控制,事务对数据加锁,阻塞其他事务
  • MVCC:乐观并发控制,每个事务看到的是数据的历史快照,不阻塞读取

两者的关系:MVCC 解决读-读并发(完全不需要锁),锁机制解决读-写/写-写并发。

4.2 事务开启方式

-- 快照读:读取历史版本,不加锁
SELECT ...;

-- 当前读:读取最新版本,加锁
SELECT ... LOCK IN SHARE MODE;
SELECT ... FOR UPDATE;
INSERT ...;
UPDATE ...;
DELETE ...;

4.3 MVCC 的核心组件

  1. 隐藏列:每行数据有 trx_id(最近修改的事务ID)和 roll_pointer(指向 undo log 的指针)
  2. undo log 链:通过 roll_pointer 连接历史版本
  3. ReadView:事务开启时生成,决定能看到哪些版本

五、一致性的保障体系 🟡

5.1 数据库层面的一致性保证

  1. 原子性:undo log 保证回滚
  2. 隔离性:锁 + MVCC 保证并发正确性
  3. 持久性:redo log + 刷盘保证已提交数据不丢失
  4. 约束检查:唯一键、外键、CHECK 约束等数据库内置约束

5.2 两阶段提交保证

BEGIN;
UPDATE users SET balance = balance - 500 WHERE id = 1;
UPDATE users SET balance = balance + 500 WHERE id = 2;
COMMIT;

两阶段提交流程:

1. 写入 redo log(prepare 状态)
2. 写入 binlog
3. 提交事务(commit 状态)

崩溃恢复时:

  • redo log prepare + binlog 有 → 提交
  • redo log prepare + binlog 无 → 回滚
  • redo log commit → 提交

【面试官心理】 问 ACID 的候选人里,能准确说出 redo log 和 undo log 分工的占 60%,能说清刷盘策略的占 30%,能说清两阶段提交细节的占 10%。这道题从 P5 问到 P7 都有很好的区分度。


级别考察重点期望回答
P5概念记忆ACID 四个特性定义
P6实现机制undo log → 原子性,redo log → 持久性,锁/MVCC → 隔离性
P7深度细节刷盘策略、两阶段提交、崩溃恢复流程