并发与事务隔离
一、并发带来的问题
1.丢失更新
定义:两个事务同时读取同一数据,然后分别基于原始值进行修改,后提交的事务覆盖了先提交的修改。
典型场景:计数器、余额扣减。
示例:
T1 读 A=100;T2 读 A=100;T1 更新 A=200 并提交;T2 更新 A=300 并提交 → 最终 A=300,T1 的更新丢失。
阻止:提高隔离级别至 可重复读 或可串行化,并加锁
2.脏读
定义:一个事务读取了另一个未提交事务写入的数据。如果后来那个事务回滚,读到的数据就是无效的。
示例:
T1 修改 A=200(未提交);T2 读取 A=200;T1 回滚 → T2 读到了脏数据。
危害:数据不一致,可能导致错误决策。
阻止:隔离级别 ≥ 读已提交(READ COMMITTED)。
3.不可重复读
定义:同一事务内,两次读取同一行数据,期间另一个事务修改并提交了该行,导致两次结果不同。
示例:
T1 读 A=100;T2 修改 A=200 并提交;T1 再次读 A=200 → 两次结果不一致。
阻止:隔离级别 ≥ 可重复读(REPEATABLE READ)
4.幻读
定义:同一事务内,两次执行同一个范围查询,期间另一个事务插入/删除满足条件的新行,导致第二次查询返回不同的行集。
示例:
T1 查询 WHERE age=20 得到 3 行;T2 插入一条 age=20 的新行并提交;T1 再次查询得到 4 行 → 出现"幻影"行。
区别不可重复读:
-
不可重复读:同一行内容被修改。
-
幻读:行集合发生变化(插入/删除)。
阻止:隔离级别 可串行化(SERIALIZABLE)
二、事务隔离级别
1.读未提交(Read Uncommitted)
-
允许一个事务读取另一个未提交事务的修改。
-
解决的问题:几乎不解决任何并发问题。
-
可能存在的异常:脏读、不可重复读、幻读、丢失更新都可能发生。
-
适用场景:对数据一致性要求极低,但需要极高并发读的场景(极少使用)
2.读已提交(Read Committed)
-
只能读取已经提交的数据,不允许读取未提交的修改。
-
解决的问题:脏读。
-
仍可能存在的异常:不可重复读、幻读、丢失更新(后两种在某些实现中可能部分避免,但标准下仍可能)。
-
注意:这是很多数据库(如 PostgreSQL、Oracle)的默认隔离级别。
3.可重复读(Repeatable Read)
-
保证在同一事务内,多次读取同一行数据的结果是一致的。事务开始后,其他事务不能修改当前事务已读取的行(通常通过行锁或快照实现)。
-
解决的问题:脏读、不可重复读。
-
仍可能存在的异常:幻读(标准 SQL 下允许幻读;但部分数据库如 MySQL InnoDB 通过间隙锁也在该级别下避免了幻读)。丢失更新在标准中也不会发生(因为可重复读通常意味着读取时加锁或使用快照,更新时加锁)。
-
注意:MySQL InnoDB 的默认隔离级别。
4.可串行化(Serializable)
-
最高的隔离级别,强制事务串行执行(或通过某种机制达到与串行执行等价的效果,如两阶段锁或 MVCC 加冲突检测)。所有并发事务的效果等同于按某个顺序依次执行。
-
解决的问题:脏读、不可重复读、幻读、丢失更新全部避免。
-
代价:并发性能最低,可能产生大量锁等待或超时。
三、各事务隔离级别优缺点以及选择
1.读未提交
-
优点:并发性能最高,无锁开销。
-
缺点:允许脏读、不可重复读、幻读,数据一致性最差。
-
适用:几乎不用,仅适用于对数据一致性无任何要求的特殊场景。
2.读已提交
-
优点:解决脏读,并发性能高(读不加锁),是 Oracle、PostgreSQL 的默认级别。
-
缺点:仍可能出现不可重复读、幻读及丢失更新。
-
适用:大多数在线事务处理系统(OLTP),如用户发帖、点赞、订单查询等。
3.可重复读
-
优点:解决脏读和不可重复读;在 MySQL InnoDB 中通过间隙锁还能防止幻读。一致性较高。
-
缺点:间隙锁可能增加锁冲突和死锁概率,并发性能低于读已提交。
-
适用:需要同一事务内多次读取结果一致的场景(如财务报表),以及 MySQL 的默认推荐。
4.可串行化
-
优点:解决所有并发问题(脏读、不可重复读、幻读、丢失更新),一致性最高。
-
缺点:并发性能最低,大量锁等待或超时。
-
适用:极少数对一致性要求极高且并发很低的场景(如金融短事务)。
总结
-
读未提交:脏读 ✔、不可重复读 ✔、幻读 ✔、丢失更新 ✔
-
读已提交:脏读 ✘、不可重复读 ✔、幻读 ✔、丢失更新 ✔(标准下)
-
可重复读:脏读 ✘、不可重复读 ✘、幻读 ✔(标准下)、丢失更新 ✘(标准下)
-
可串行化:全部 ✘
注:"✔"表示可能发生,"✘"表示该级别已解决此问题。实际数据库实现可能因具体机制(如 MVCC、间隙锁)而有所增强,例如 MySQL InnoDB 的 REPEATABLE READ 也能防止幻读。