mysql如何避免不可重复读_mysql读已提交与可重复读区别

2次阅读

不可重复读在mysql的rr级别下发生于混用快照读与当前读:普通select基于事务启动时的一致性视图,而select…for update等当前读直接读取最新已提交数据,导致同一事务内两次读结果不一致。

mysql如何避免不可重复读_mysql读已提交与可重复读区别

不可重复读在 MySQL 里到底怎么发生的

可重复读(RR)隔离级别下,SELECT 看不到其他事务已提交的更新——这是 MySQL 默认行为,但前提是**你用的是普通快照读(也就是没加锁的 SELECT)**。一旦用了 SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE,就变成当前读,会看到最新已提交数据,这时候“不可重复读”就可能重现。

常见错误现象:SELECT 第一次查到 amount = 100,中间别人 UPDATECOMMIT200,第二次 SELECT 还是 100(正常),但如果你第二次写成 SELECT ... FOR UPDATE,就会查到 200,看起来像“不可重复读”,其实只是读模式变了。

  • RR 隔离级别靠 MVCC + 一致性视图(read view)实现,每个事务启动时拍一个快照
  • 快照只对普通 SELECT 生效;所有带锁读、UPDATEdelete 都走当前读,绕过快照
  • 如果业务逻辑混用快照读和当前读(比如先查再锁更新),就容易误以为隔离失效

READ COMMITTED 和 REPEATABLE READ 的关键行为差异

区别不在“能不能读到新数据”,而在“什么时候拍快照”和“锁的粒度”。RC 每次 SELECT 都新建 read view,RR 只在事务第一个 SELECT(或 DML)时建一次。

  • READ COMMITTED:非唯一条件 UPDATE 只锁匹配行,不锁间隙;REPEATABLE READ 下默认会加间隙锁(gap lock),防止幻读
  • SELECT count(*) 在 RC 下每次重算,在 RR 下可能复用快照(尤其配合 innodb_locks_unsafe_for_binlog=ON 时行为更复杂)
  • RC 更易发生不可重复读,但并发更新冲突少;RR 更“稳”,但锁范围大,容易死锁
  • MySQL 8.0+ 中,READ COMMITTED 对二级索引的 next-key 锁也退化为 record lock,进一步降低锁强度

怎么真正避免不可重复读(而不是假装避免)

如果业务要求同一事务内多次读必须完全一致,不能只依赖隔离级别,得控制读方式和事务边界。

  • 全程用普通 SELECT(不加锁),且确保事务别太长——长事务导致 read view 持久太久,可能拖慢 purge 线程,间接影响性能
  • 避免在事务中穿插 SELECT ... FOR UPDATE 后又普通 SELECT,这种混合读法会让语义混乱
  • 真要强一致性,不如显式用 SELECT ... LOCK IN SHARE MODE 把数据“固定住”,但要注意它会阻塞并发写,不是零成本方案
  • 应用层缓存(如 redis)不能替代事务隔离,缓存过期策略和 DB 更新不同步时,反而放大不一致风险

哪些场景下 RR 也救不了你

MySQL 的可重复读不是银弹。遇到这些情况,即使设了 REPEATABLE READ,结果照样“变”:

  • 跨事务修改同一行并提交后,你在本事务中执行 UPDATE ... WHERE,InnoDB 会用当前读去查最新值做判断,可能触发唯一键冲突或条件不匹配
  • 使用 SELECT ... FOR UPDATE 时,如果 where 条件没走索引,会升级为表级锁,行为偏离预期
  • 从库延迟导致主从不一致,和隔离级别无关,但开发者常误判为 RR 失效
  • autocommit=1 下每个语句都是独立事务,谈不上“重复读”——这点最容易被忽略

事务开始时机、锁类型、是否走索引、binlog 格式(STATEMENT vs ROW)、甚至 innodb_foo 类配置都可能悄悄改掉你预期的行为。别只盯着隔离级别设成什么,得看 SQL 怎么写、怎么跑、在哪跑。

text=ZqhQzanResources