mysql不可重复读如何理解_mysql事务示例解析

2次阅读

不可重复读是指同一个事务中两次执行相同select语句却读到不同结果,因其他事务提交了update/delete;它读的是已提交数据,仅影响单行值变化,不增删行。

mysql不可重复读如何理解_mysql事务示例解析

什么是不可重复读:一句话说清现象

不可重复读是指:**同一个事务中,两次执行完全相同的 SELECT 语句,却读到不同的结果**——不是因为自己改了数据,而是其他事务在你两次查询之间 UPDATEDELETE 并提交了那行数据。

它和脏读的区别在于:不可重复读读到的是**已提交的数据**;和幻读的区别在于:它只影响**单行记录的值变化**(比如余额从 100 → 80),不涉及行数增减。

用真实 sql 演示不可重复读全过程

假设表 account 有一条记录:id=1, balance=100。两个会话并发执行:

-- 会话 A(事务1) START TRANSACTION; SELECT balance FROM account WHERE id = 1;  -- 返回 100 -- 此时暂停,不提交 <p>-- 会话 B(事务2) START TRANSACTION; UPDATE account SET balance = 80 WHERE id = 1; COMMIT;</p><p>-- 回到会话 A SELECT balance FROM account WHERE id = 1;  -- 返回 80 ← 不可重复读发生! COMMIT;

关键点:

  • 会话 A 的两次 SELECT 语句一模一样,但结果不同
  • 会话 B 的 UPDATECOMMIT,所以不是脏读
  • 没新增/删除行,所以不是幻读

为什么默认隔离级别(REPEATABLE READ)能防它?

mysql InnoDB 默认是 REPEATABLE READ 隔离级别,它靠 **MVCC(多版本并发控制)** 实现:事务启动时拍一个“快照”,后续所有 SELECT 都基于这个快照读,不受其他事务已提交修改的影响。

但注意:这个“快照”是**语句级还是事务级**?InnoDB 是**事务级快照**——即第一次 SELECT 才建立快照,不是 START TRANSACTION 就建。不过在 RR 级别下,只要没执行过 SELECT,第一次读就会固定快照,之后都一致。

容易踩的坑:

  • 如果误设成 READ COMMITTED(如 postgresql 默认),就一定会出现不可重复读
  • SELECT ... for UPDATELOCK IN SHARE MODE 会绕过 MVCC,走当前读,可能“看到”新值——这不是 bug,是设计如此
  • RR 级别下,UPDATE 语句本身是“当前读”,它会看到最新已提交版本,所以更新逻辑仍可能受干扰

什么时候真要担心不可重复读?

典型场景是业务逻辑依赖“读-判-写”三步,且中间不能被别人改:

  • 电商下单扣库存:SELECT stock FROM item WHERE id=123 → 判断 ≥1 → UPDATE item SET stock = stock - 1
  • 账户转账:SELECT balance FROM user WHERE id=456 → 判断余额够 → UPDATE user SET balance = balance - 100

如果隔离级别不够(如 RC),第二步判断后、第三步执行前,别人把余额改了,就可能超扣或透支。这时要么升到 REPEATABLE READ,要么加锁(SELECT ... FOR UPDATE),要么用乐观锁(version 字段)。

真正容易被忽略的一点:**应用层重试 + 补偿逻辑,比死磕隔离级别更常见也更可靠**——尤其在微服务跨库场景,数据库隔离级别管不到服务间状态。

text=ZqhQzanResources