两个事物修改同一条记录,这个时候再去读这个记录会怎么样 ?读到的结果是一样的还是不一样的?
两个事务修改同一条记录后,再去读这条记录的结果取决于 数据库的隔离级别 和 事务执行的时序。结果可能一样(读到最新提交值),也可能不一样(读到不同版本或未提交值)。常见隔离级别下:
- 读未提交:可能读到未提交的修改(脏读),结果不一样。
- 读已提交:读到已提交的最新值,结果可能随提交顺序变化。
- 可重复读:读到事务开始时的版本,结果一致但可能不是最新。
- 串行化:严格顺序执行,结果一致且最新。
关键事实
- 并发修改:
- 两个事务(T1、T2)同时修改同一条记录,涉及锁或 MVCC(多版本并发控制)。
- 读时机:
- 读发生在修改前、中、后,结果不同。
- 隔离级别影响:
- 数据库(如 MySQL InnoDB)通过隔离级别控制读到的数据版本。
具体分析(以 MySQL InnoDB 为例)
初始数据
- 表 account,记录:id=1, balance=100。
1. 读未提交(Read Uncommitted)
- 场景:
- T1:UPDATE account SET balance = 200 WHERE id = 1;(未提交)。
- T2:UPDATE account SET balance = 300 WHERE id = 1;(未提交)。
- T3:SELECT balance FROM account WHERE id = 1;。
- 结果:T3 可能读到 200 或 300(脏读),取决于谁先写。
- 特点:读到未提交数据,结果不一样且不稳定。
2. 读已提交(Read Committed)
- 场景:
- T1:UPDATE balance = 200;(提交)。
- T2:UPDATE balance = 300;(未提交)。
- T3:SELECT balance;。
- 结果:T3 读到 200(最新提交值),若 T2 后提交,T3 再次读可能是 300。
- 特点:避免脏读,但可能不可重复读,结果随提交时序变化。
3. 可重复读(Repeatable Read,InnoDB 默认)
- 场景:
- T3 开始事务。
- T1:UPDATE balance = 200;(提交)。
- T2:UPDATE balance = 300;(提交)。
- T3:SELECT balance;(两次)。
- 结果:T3 两次读到 100(事务开始时的快照),与 T1、T2 修改无关。
- 特点:MVCC 提供快照隔离,结果一致但可能不是最新。
4. 串行化(Serializable)
- 场景:
- T1 和 T2 修改顺序执行(加锁阻塞)。
- T3 读发生在 T1、T2 提交后。
- 结果:读到 300(最后提交值),结果一致且最新。
- 特点:完全隔离,性能最低。
面试角度
- 锁机制:
- 悲观锁:T1 修改加锁,T2 阻塞,T3 读最新提交值。
- 乐观锁:T1、T2 提交时检查冲突,T3 读取决于提交成功者。
- MVCC:
- InnoDB 用版本号(事务 ID)隔离,T3 读快照。
- 实际影响:
- 读未提交:脏读风险,数据不一致。
- 可重复读:避免不可重复读,但可能幻读。
- 性能对比:
- 低隔离级别快但一致性差,高隔离级别慢但安全。
- 面试点:
- 问“读到什么”时,提隔离级别。
- 问“如何保证一致”时,提串行化或锁。