mysql并发场景下不可重复读如何解决_mysql隔离策略说明

8次阅读

不可重复读是指同一事务中多次 SELECT 相同语句却读到其他事务已提交的更新,导致结果不一致;MySQL 默认 REPEATABLE READ 通过 MVCC 快照读避免该问题,但当前读(如 SELECT … FOR UPDATE)仍可能感知更新。

mysql 并发场景下不可重复读如何解决_mysql 隔离策略说明

什么是不可重复读,它在 MySQL 中具体表现 为什么

不可重复读是指:同一个事务中,多次执行相同的 SELECT 语句,却读到了其他事务已提交的修改结果,导致前后两次查询结果不一致。这不是幻读(幻读是读到了新插入的行),而是对 ** 已有行的更新被感知到了 **。

典型场景:SELECT → 其他事务 UPDATE …… COMMIT → 再次 SELECT,发现同一行数据变了。这在 READ COMMITTED 隔离级别下合法且常见。

MySQL 默认隔离级别能否避免不可重复读

MySQL 默认隔离级别是 REPEATABLE READ,它通过 MVCC + Next-Key Lock(在可重复读下对范围查询加锁)来防止不可重复读——但仅限于 ** 快照读(普通 SELECT)**。

注意以下关键点:

  • REPEATABLE READ 下,普通 SELECT 基于事务开启时的 Read View,不会看到其他事务后续的提交,因此能保证不可重复读不发生
  • SELECT …… FOR UPDATESELECT …… LOCK IN SHARE MODE 是当前读,会绕过 MVCC 快照,直接读最新已提交版本,并加锁,此时可能“感知”到其他事务的更新(取决于加锁时机和范围)
  • 如果业务混用快照读与当前读,或依赖 SELECT 结果做后续判断再 UPDATE(即“读 - 改 - 写”逻辑),仍可能因条件竞态导致逻辑错误,这不是隔离级别失效,而是应用层未正确使用锁或未用 SELECT …… FOR UPDATE

真正解决并发下的不可重复读问题,该怎么做

靠隔离级别本身只能缓解,不能根治所有业务场景。必须结合具体操作方式:

  • 对需要“读取后立即修改”的逻辑,务必用 SELECT …… FOR UPDATE(在主键 / 索引列上)发起当前读并加行锁,确保从读到写之间该行不被其他事务修改
  • 避免在 REPEATABLE READ 下先普通 SELECT 拿值,再拼 UPDATE 条件——这存在窗口期,应改用原子语句如 UPDATE …… WHERE id = ? AND status = 'pending'
  • 若涉及多行一致性(如转账需查两账户余额),单靠行锁不够,需用 SELECT …… FOR UPDATE 锁住所有相关行,且按固定顺序(如 ID 升序)加锁,防死锁
  • 高并发下过度依赖 SELECT …… FOR UPDATE 可能导致锁等待甚至超时,此时应评估是否可用乐观锁(如 version 字段 + UPDATE …… SET version = version + 1 WHERE id = ? AND version = ?)替代

不同隔离级别对不可重复读的实际影响对比

MySQL 支持的四种隔离级别对不可重复读的控制能力如下:

  • READ UNCOMMITTED:能看到未提交变更,必然出现不可重复读(以及脏读)
  • READ COMMITTED:每次 SELECT 都新建 Read View,能看到其他事务已提交的更新 → 会出现不可重复读
  • REPEATABLE READ(MySQL 默认):事务启动时创建 Read View,后续快照读都复用 → 普通 SELECT 不会出现不可重复读
  • SERIALIZABLE:所有 SELECT 隐式转为 SELECT …… LOCK IN SHARE MODE,强制串行化 → 理论上杜绝,但性能代价极大,极少使用

真正容易被忽略的是:即使在 REPEATABLE READ 下,只要用了 UPDATEDELETE 或当前读,就不再受快照保护;而业务代码里往往没意识到自己触发了当前读。

text=ZqhQzanResources