聊一聊数据库事务机制
# 题)
多个事务并发执行时,若缺乏隔离机制,会出现 3 类数据异常:
- 脏读(Dirty Read):事务 A 读取到事务 B 未提交的修改数据,若事务 B 回滚,事务 A 读取的是 “脏数据”;
- 示例:事务 B 修改用户余额为 200 元(未提交),事务 A 读取到 200 元,事务 B 回滚后,用户余额实际为 100 元,事务 A 读取的 200 元是脏数据。
- 不可重复读(Non-Repeatable Read):事务 A 多次读取同一数据,事务 B 在期间修改并提交该数据,导致事务 A 多次读取结果不一致;
- 示例:事务 A 第一次读取用户余额为 100 元,事务 B 修改并提交为 200 元,事务 A 再次读取为 200 元,两次结果不一致。
- 幻读(Phantom Read):事务 A 多次查询同一范围数据,事务 B 在期间插入 / 删除该范围数据并提交,导致事务 A 多次查询的结果集行数不一致;
- 示例:事务 A 第一次查询 “age>20” 的用户有 10 行,事务 B 插入 1 行并提交,事务 A 再次查询有 11 行,结果集行数变化。
# 3. 事务隔离级别(解决并发异常的方案)
MySQL 定义了 4 种隔离级别,从低到高依次为:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 实现原理(InnoDB) |
|---|---|---|---|---|
| 读未提交(Read Uncommitted) | 允许 | 允许 | 允许 | 无锁,直接读取最新数据 |
| 读已提交(Read Committed) | 禁止 | 允许 | 允许 | MVCC(每次查询生成新 read_view) |
| 可重复读(Repeatable Read) | 禁止 | 禁止 | 禁止(默认) | MVCC(事务内共用一个 read_view)+ 间隙锁 |
| 串行化(Serializable) | 禁止 | 禁止 | 禁止 | 表级锁,事务串行执行 |
- 默认隔离级别:MySQL 默认隔离级别为 “可重复读(RR)”,InnoDB 通过 MVCC 和间隙锁解决所有并发异常;
- 隔离级别与性能:隔离级别越高,并发性能越低(如串行化隔离级别下,事务需排队执行),需根据业务场景选择(如金融交易选 RR 或串行化,普通查询选 RC)。
# 4. 事务的实现机制(InnoDB)
InnoDB 通过 “日志机制” 和 “锁机制” 实现事务的 ACID 特性:
- (1)原子性与回滚:undo 日志:
- undo 日志记录事务执行前的数据状态(如修改前的余额为 100 元);
- 若事务执行失败(如抛出异常),InnoDB 通过 undo 日志回滚数据(将余额恢复为 100 元)。
- (2)持久性:redo 日志:
- redo 日志记录事务执行后的修改操作(如将余额从 100 元改为 200 元);
- 事务提交时,InnoDB 先将 redo 日志写入磁盘(“日志先行” 原则),再修改内存中的数据;
- 即使数据库崩溃,重启后可通过 redo 日志重放修改操作,确保数据持久化。
- (3)隔离性:MVCC 与锁机制:
- MVCC 通过多版本数据实现 “读不加锁”,避免脏读和不可重复读;
- 锁机制(行锁、间隙锁)避免并发写操作冲突,解决幻读。
- (4)一致性:ACID 的最终目标:
- 原子性、隔离性、持久性共同确保一致性(如转账操作要么全部成功,要么全部失败,且并发执行时数据不混乱);
- 此外,业务逻辑(如 “余额不能为负”)也需通过约束(如 CHECK 约束)或代码确保一致性。
# 5. 事务的常用操作
-- 1. 开启事务(默认自动提交关闭)
START TRANSACTION; -- 或 BEGIN;
-- 2. 执行事务操作(如转账:扣除A的余额,增加B的余额)
UPDATE user SET balance = balance - 100 WHERE id = 1; -- A扣100
UPDATE user SET balance = balance + 100 WHERE id = 2; -- B加100
-- 3. 提交事务(修改持久化)
COMMIT;
-- 4. 若操作失败,回滚事务(恢复到事务开始前状态)
-- ROLLBACK;
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
#
上次更新: 12/30/2025