数据库事务与隔离级别
事务是一组 SQL 的逻辑单元:要么全部提交,要么全部回滚。详解 数据库事务。
ACID
| 特性 | 含义 | InnoDB 实现 |
|---|---|---|
| A 原子性 | 全成功或全回滚 | undo log |
| C 一致性 | 数据满足约束与业务规则 | A+I+D + 约束 |
| I 隔离性 | 并发事务互不干扰 | MVCC + 锁 |
| D 持久性 | 提交后宕机不丢 | redo log(WAL) |
并发读问题
| 问题 | 描述 | 哪级隔离能防 |
|---|---|---|
| 脏读 | 读到别的事务未提交的数据 | RC 及以上 |
| 不可重复读 | 同事务两次读同一行,结果不同 | RR 及以上 |
| 幻读 | 同事务两次范围查询,行数不同 | Serializable;MySQL RR+间隙锁大部分防 |
| 丢失更新 | 两事务读-改-写,后者覆盖前者 | 乐观锁 / FOR UPDATE |
四种隔离级别
| 级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
| READ UNCOMMITTED | ❌ | ❌ | ❌ | 几乎不用 |
| READ COMMITTED | ✅ | ❌ | ❌ | Oracle 默认;每次读新快照 |
| REPEATABLE READ | ✅ | ✅ | ⚠️ | MySQL InnoDB 默认 |
| SERIALIZABLE | ✅ | ✅ | ✅ | 串行,性能差 |
SELECT @@transaction_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;MVCC(快照读)
InnoDB 通过 多版本 + ReadView 让普通 SELECT 不加锁(快照读),提高并发。
当前行 (trx_id=100)
↓ undo 链
trx_id=80 → trx_id=50 → ...
ReadView 记录活跃事务 ID,按规则判断版本是否对当前事务可见。
| 隔离级别 | ReadView 时机 |
|---|---|
| RC | 每次 SELECT 新建 |
| RR | 事务第一次读时创建,之后复用 |
当前读(UPDATE / DELETE / SELECT ... FOR UPDATE)走锁,不走 MVCC 快照。
锁与幻读(RR)
| 锁 | 作用 |
|---|---|
| 行锁 | 锁索引记录 |
| 间隙锁 | 锁记录间空隙,防插入 |
| Next-Key Lock | 行锁 + 间隙锁(RR 默认) |
因此 MySQL RR 下「幻读」在 InnoDB 里大部分被抑制(面试常考与标准教科书的差异)。
Spring 事务 vs 数据库事务
| 层次 | 作用 |
|---|---|
| 数据库 | 单 Connection 上的 ACID |
@Transactional | AOP 管理何时 begin/commit/rollback |
传播行为 REQUIRED、REQUIRES_NEW 等见 Spring @Transactional 传播行为;失效场景见 SpringBoot事务失效场景。
常见面试题
Q:只设置了 RR,为什么还会幻读?
A:标准定义下 RR 仍可能幻读;InnoDB 用间隙锁在当前读场景防插入;纯快照读看到的是固定版本,不存在「新行插入进快照」的幻读表现。
Q:RC 和 RR 怎么选?
A:要读最新已提交数据用 RC(Oracle 风格);要同事务内 repeatable 读用 RR(MySQL 默认)。
Q:长事务有什么危害?
A:undo 链变长、锁持有久、死锁概率升、主从延迟;事务内勿做 HTTP/MQ。