脏读(Dirty Read)
定义:读到了其他事务未提交的数据
A事务(改,未提交) → B事务(读,读到未提交数据)→A事务(回滚,结束) =B的事务是不存在的数据
T1修改A余额:1000 → 900(未提交)
T2读取A余额 → 看到900元
T1突然回滚(比如超时失败)→ A余额恢复为1000元
T2拿着错误的900元做决策 → 系统崩溃!
解决: 🔒 设置隔离级别为
READ_COMMITTED
→ 只能读到其他事务已提交的数据。
不可重复读(Non-Repeatable Read)
定义:同一事务内,两次读同一数据结果不同(像数据自己会变魔术)
A事务(读,第一次数据)→B事务(改,已提交)→A(读,第二次读) =A事两次数据读内容不一致
T2第一次读A余额 → 1000元
T1提交转账:A余额900元(已提交)
T2第二次读A余额 → 900元
T2懵了:刚才还是1000,现在变900?报表对不上!
解决: 🔒 隔离级别升到
REPEATABLE_READ
→ 保证事务内多次读取数据值不变(MySQL默认级别)。
幻读(Phantom Read)
定义:同一事务内,两次查询符合条件的记录数不同(像凭空多出幽灵数据)
A事务(读行数,未提交)→B事务(添加了几行,已提交)→A事务(在读,和直接行数不一样) =A一样的条件两次读取行数不一致
T2查询“余额<600的账户数” → 结果1个(只有B账户500元)
T1提交转账:A余额从1000→300(A也满足<600了!)
T2再次查询 → 结果变成2个(A和B)
T2怀疑人生:刚才明明是1个!
解决: 🔒 隔离级别升到
SERIALIZABLE
(完全串行)或 MySQL用间隙锁(Gap Lock) 锁住查询范围。💡 不可重复读 vs 幻读核心区别:
不可重复读:同一条数据的内容变了(余额从1000→900) 幻读:数据数量变了(多出一条A账户)
脏写(Dirty Write) → 最危险!
定义:两个事务同时修改同一数据,导致修改覆盖(像两人同时编辑文档不保存)
A事务(改,未提交)→B事务(改,未提交)→A事务(提交)→B事务(提交) =A事务提交的数据丢失
T1修改A余额:1000 → 900
T2也修改A余额:1000 → 800(未提交的脏数据上修改!)
T1提交 → A=900
T2提交 → A=800(T1的转账被覆盖!钱丢了!)
解决: 🔒 所有隔离级别都禁止脏写! 底层用行锁(Row Lock) 保证:修改数据时独占锁定(写锁)。
隔离级别 | 脏读 | 不可重复读 | 幻读 | 脏写 |
---|---|---|---|---|
READ_UNCOMMITTED | ❌ | ❌ | ❌ | ✅ |
READ_COMMITTED | ✅ | ❌ | ❌ | ✅ |
REPEATABLE_READ | ✅ | ✅ | ❌ | ✅ |
SERIALIZABLE | ✅ | ✅ | ✅ | ✅ |
✅ = 解决 ❌ = 可能发生
实际项目中的应用
-
支付系统:必须用
REPEATABLE_READ
→ 防止重复扣款(不可重复读) -
库存扣减:需
SERIALIZABLE
或 乐观锁 → 防止超卖(幻读) -
银行核心系统:连读操作也用
SERIALIZABLE
→ 绝对数据一致性(牺牲性能保安全)
速记口诀:
一脏二不可三幻四覆盖
脏读(读未提交)
不可重复读(同数据值变)
幻读(数据量变)
脏写(覆盖修改)