跳到主要内容

事务与隔离级别

问题

什么是数据库事务?MySQL 的四种隔离级别分别解决了什么问题?

答案

一、事务的 ACID 特性

特性含义MySQL 实现机制
原子性(Atomicity)事务要么全部执行,要么全部不执行undo log — 记录旧值,回滚时恢复
一致性(Consistency)事务前后数据库保持一致状态由其他三个特性共同保证
隔离性(Isolation)并发事务互不干扰MVCC + 锁
持久性(Durability)事务提交后数据永久保存redo log — WAL 机制保证崩溃恢复
面试要点

ACID 四个特性的 实现机制 是面试常问点:

  • A → undo log
  • I → MVCC + 锁
  • D → redo log
  • C → 由 A + I + D 保证

二、并发事务的问题

问题描述示例
脏读读到其他事务 未提交 的数据事务 A 修改了数据但未提交,事务 B 读到了修改后的值,A 回滚后 B 读到的就是脏数据
不可重复读同一事务内两次读取 同一行,结果不同事务 A 读 id=1 的 name='张三',事务 B 修改 name='李四' 并提交,事务 A 再读变成了'李四'
幻读同一事务内两次查询 行数 不同事务 A 查询 age>20 得到 5 行,事务 B 插入一行 age=25 并提交,事务 A 再查得到 6 行

区分不可重复读和幻读

  • 不可重复读:针对 同一行 数据的 修改(UPDATE)
  • 幻读:针对 行数增减(INSERT/DELETE)

三、四种隔离级别

隔离级别脏读不可重复读幻读实现方式
读未提交 (READ UNCOMMITTED)❌ 可能❌ 可能❌ 可能无 MVCC,直接读最新数据
读已提交 (READ COMMITTED)✅ 解决❌ 可能❌ 可能MVCC:每条 SQL 生成新的 Read View
可重复读 (REPEATABLE READ)✅ 解决✅ 解决⚠️ 部分解决MVCC:事务开始时生成 Read View
串行化 (SERIALIZABLE)✅ 解决✅ 解决✅ 解决读加共享锁,写加排他锁
-- 查看当前隔离级别
SELECT @@transaction_isolation;

-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
MySQL 默认隔离级别

MySQL 默认使用 REPEATABLE READ(可重复读),通过 MVCC + Next-Key Lock 在很大程度上解决了幻读。而 Oracle/PostgreSQL 默认使用 READ COMMITTED。

四、InnoDB 在 RR 级别如何解决幻读

InnoDB 在 REPEATABLE READ 下通过两种机制配合解决幻读:

读类型机制说明
快照读(SELECT)MVCC Read View始终读事务开始时的一致性快照
当前读(SELECT FOR UPDATE / INSERT / UPDATE / DELETE)Next-Key Lock(行锁 + 间隙锁)锁定范围,阻止其他事务在范围内插入
-- 快照读:不加锁,读 MVCC 快照
SELECT * FROM users WHERE age > 20;

-- 当前读:加 Next-Key Lock
SELECT * FROM users WHERE age > 20 FOR UPDATE;
INSERT INTO users (name, age) VALUES ('新人', 25);
UPDATE users SET name = '新名' WHERE age > 20;
RR 下幻读仍可能发生的情况
-- 事务 A
BEGIN;
SELECT * FROM users WHERE id = 5; -- 不存在,返回空(快照读)

-- 事务 B
INSERT INTO users (id, name) VALUES (5, '张三');
COMMIT;

-- 事务 A(继续)
UPDATE users SET name = '李四' WHERE id = 5; -- 成功!(当前读能看到 B 的插入)
SELECT * FROM users WHERE id = 5; -- 看到 id=5(MVCC 版本链更新了)
COMMIT;

这种场景下,事务 A 第一次快照读看不到 id=5,但 UPDATE 是当前读能修改到 id=5 的行,之后再次 SELECT 就能看到了 — 这就是 RR 下幻读的边界情况。

五、事务的使用

-- 显式事务
BEGIN; -- 或 START TRANSACTION
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT; -- 提交

-- 出错时回滚
ROLLBACK;

-- 保存点(部分回滚)
BEGIN;
SAVEPOINT sp1;
UPDATE ...;
SAVEPOINT sp2;
UPDATE ...;
ROLLBACK TO sp2; -- 只回滚到 sp2,sp1 的修改保留
COMMIT;

常见面试问题

Q1: READ COMMITTED 和 REPEATABLE READ 的 MVCC 区别?

答案

核心区别在于 Read View 的生成时机

  • RC:每条 SELECT 语句都创建新的 Read View → 能看到其他事务已提交的修改
  • RR:整个事务第一条 SELECT 时创建 Read View,后续复用 → 事务内始终看到同一个快照

详见 MVCC 章节

Q2: 为什么大多数互联网公司建议用 RC 而不是默认的 RR?

答案

  1. RC 锁粒度更小:不需要间隙锁(Gap Lock),并发度更高
  2. RC 下可以用 binlog_format=ROW:解决主从数据一致性问题
  3. RR 的间隙锁容易导致死锁:特别是在批量操作时
  4. RC 更符合直觉:总是读到最新已提交数据

Q3: 长事务有什么危害?

答案

  1. undo log 膨胀:长事务持有 Read View,旧版本无法回收
  2. 锁持有时间长:阻塞其他事务
  3. 回滚代价大:写了大量 redo log 和 undo log
  4. 主从延迟:长事务提交时 binlog 一次性发送

排查长事务

-- 查找超过 60 秒的事务
SELECT * FROM information_schema.innodb_trx
WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60;

相关链接