在数据库系统中,当多个事务同时访问相同数据时,可能引发并发事务问题。以下是主要的并发问题及其原理:
1. 脏读(Dirty Read)
- 问题:事务A读取了事务B未提交的数据,事务B随后回滚
- 后果:事务A读到了无效的"脏数据"
- 示例:
-- 事务B(未提交) UPDATE users SET balance = 500 WHERE id = 1; -- 未提交 -- 事务A(读取未提交数据) SELECT balance FROM users WHERE id = 1; -- 读到500(脏数据) -- 事务B回滚 ROLLBACK; -- balance实际恢复为1000
2. 不可重复读(Non-repeatable Read)
- 问题:事务A多次读取同一数据,期间事务B修改并提交了该数据
- 后果:事务A两次读取结果不一致
- 示例:
-- 事务A第一次读取 SELECT points FROM users WHERE id = 1; -- 返回100 -- 事务B更新并提交 UPDATE users SET points = 200 WHERE id = 1; COMMIT; -- 事务A第二次读取 SELECT points FROM users WHERE id = 1; -- 返回200(结果改变)
3. 幻读(Phantom Read)
- 问题:事务A按条件查询数据,期间事务B新增/删除了符合条件的数据并提交
- 后果:事务A两次查询的结果集数量/内容不一致
- 示例:
-- 事务A第一次查询 SELECT COUNT(*) FROM orders WHERE user_id = 1; -- 返回3条 -- 事务B插入新订单并提交 INSERT INTO orders(user_id) VALUES (1); COMMIT; -- 事务A第二次查询 SELECT COUNT(*) FROM orders WHERE user_id = 1; -- 返回4条(出现"幻影行")
4. 更新丢失(Lost Update)
- 问题:两个事务同时读取并修改同一数据,后提交的事务覆盖了前者的修改
- 类型:
- 第一类丢失更新:事务A回滚导致事务B的更新被覆盖
- 第二类丢失更新:事务A提交后,事务B的提交覆盖了A的更新
- 示例:
-- 初始值:balance=1000 -- 事务A:增加100 SELECT balance INTO @bal FROM accounts WHERE id=1; -- 读到1000 SET @new = @bal + 100; UPDATE accounts SET balance = @new WHERE id=1; -- 写为1100 -- 事务B:增加200(同时执行) SELECT balance INTO @bal FROM accounts WHERE id=1; -- 也读到1000 SET @new = @bal + 200; UPDATE accounts SET balance = @new WHERE id=1; -- 覆盖为1200(丢失了A的+100)
问题对比表
| 问题类型 | 关键特征 | 数据变化方式 | 隔离级别影响 |
|---|---|---|---|
| 脏读 | 读取未提交数据 | 任何修改 | READ UNCOMMITTED 会发生 |
| 不可重复读 | 同记录多次读取结果不同 | 数据修改 | READ COMMITTED 会发生 |
| 幻读 | 结果集数量/内容变化 | 数据增/删 | REPEATABLE READ 可能发生 |
| 更新丢失 | 后提交覆盖先提交的修改 | 并发修改冲突 | 所有级别都可能发生(需锁解决) |
解决方案
-
隔离级别控制:
SET TRANSACTION ISOLATION LEVEL [级别]; -- 级别:READ UNCOMMITTED / READ COMMITTED / REPEATABLE READ / SERIALIZABLE -
锁机制:
- 共享锁(S锁):
SELECT ... LOCK IN SHARE MODE - 排他锁(X锁):
SELECT ... FOR UPDATE
- 共享锁(S锁):
-
乐观锁(应用层解决):
UPDATE products SET stock = stock - 10, version = version + 1 WHERE id = 100 AND version = 5; -- 检查版本号 -
MVCC(多版本并发控制):
- InnoDB通过Undo Log实现快照读
- 保证在REPEATABLE READ级别下不会出现不可重复读
各隔离级别解决能力
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 更新丢失 |
|---|---|---|---|---|
| READ UNCOMMITTED | ❌ | ❌ | ❌ | ❌ |
| READ COMMITTED | ✅ | ❌ | ❌ | ❌ |
| REPEATABLE READ | ✅ | ✅ | ❌* | ❌ |
| SERIALIZABLE | ✅ | ✅ | ✅ | ✅ |
*注:MySQL的InnoDB在REPEATABLE READ下通过Next-Key Locking解决了幻读问题
选择隔离级别需权衡数据一致性和并发性能,通常:
- 金融系统:SERIALIZABLE
- 通用系统:REPEATABLE READ(MySQL默认)
- 高并发读:READ COMMITTED

962

被折叠的 条评论
为什么被折叠?



