并发事务的问题

并发事务的问题,本质上是——多个事务同时读写同一批数据时,会产生读到“不该读”的东西或者覆盖“别人刚写的东西”

主要的并发问题:

  1. 脏读
  2. 不可重复读
  3. 幻读
  4. 丢失更新

脏读(Dirty Read)

现象:
事务A读到了事务B还没提交的数据。
如果B后面回滚了,那A读到的就是“从未存在过”的数据。

举个极简例子:

  • B:把账户余额从 100 改成 0(还没提交)
  • A:读取余额 → 看到 0
  • B:回滚 → 余额恢复 100

A看到的0是幻觉。数据库允许这种情况时,隔离级别叫 Read Uncommitted


不可重复读(Unrepeatable Read)

现象:
同一个事务中,两次读取同一行数据,结果不一样。

流程:

  • A:读取余额 = 100
  • B:修改余额为 200 并提交
  • A:再次读取余额 → 200

A在同一个事务中看到两个不同结果。

这不是“错”,但它破坏了事务的一致性直觉。
通常在 Read Committed 隔离级别下会发生。


幻读(Phantom Read)

现象:
同一个事务中,两次按条件查询,记录数量变了。

流程:

  • A:查询 age > 18,返回 10 条
  • B:插入一条 age=20 并提交
  • A:再次查询 age > 18,返回 11 条

这通常发生在范围查询下。

  • 不可重复读与幻读的区别:
    • 不可重复读的重点是内容修改或者记录减少比如多次读取同一条记录发现其中某些记录的值被修改。
    • 幻读的重点在于记录新增比如多次执行同一条查询语句时,发现查到的记录增加了。

丢失更新(Lost Update)

现象:
两个事务同时修改同一条数据,后提交的覆盖了先提交的。

流程:

  • A:读取余额 100
  • B:读取余额 100
  • A:+10 → 110 提交
  • B:+20 → 120 提交

最后结果是120,而不是130。
A的修改“被抹掉”了。


总结

问题 本质 是否读到未提交数据
脏读 读到未提交数据
不可重复读 同一行数据变化
幻读 结果集数量变化
丢失更新 写覆盖写

这些问题为什么存在

因为数据库在“性能”和“隔离性”之间做权衡。

如果完全不允许并发问题,那就只能一个事务一个事务排队执行(串行化),性能直接崩塌。

所以数据库提供了隔离级别:

  • Read Uncommitted
  • Read Committed
  • Repeatable Read
  • Serializable

比如 InnoDB 通过 MVCC(多版本并发控制)解决大部分读写冲突,用“时间线”来模拟一个一致世界。它不是阻止冲突,而是用版本把世界切片