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

什么是不可重复读:一句话说清现象
不可重复读是指:**同一个事务中,两次执行完全相同的 SELECT 语句,却读到不同的结果**——不是因为自己改了数据,而是其他事务在你两次查询之间 UPDATE 或 DELETE 并提交了那行数据。
它和脏读的区别在于:不可重复读读到的是**已提交的数据**;和幻读的区别在于:它只影响**单行记录的值变化**(比如余额从 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 的
UPDATE已COMMIT,所以不是脏读 - 没新增/删除行,所以不是幻读
为什么默认隔离级别(REPEATABLE READ)能防它?
MySQL InnoDB 默认是 REPEATABLE READ 隔离级别,它靠 **MVCC(多版本并发控制)** 实现:事务启动时拍一个“快照”,后续所有 SELECT 都基于这个快照读,不受其他事务已提交修改的影响。
本书是全面讲述PHP与MySQL的经典之作,书中不但全面介绍了两种技术的核心特性,还讲解了如何高效地结合这两种技术构建健壮的数据驱动的应用程序。本书涵盖了两种技术新版本中出现的最新特性,书中大量实际的示例和深入的分析均来自于作者在这方面多年的专业经验,可用于解决开发者在实际中所面临的各种挑战。 本书内容全面深入,适合各层次PHP和MySQL开发人员阅读,既是优秀的学习教程,也可用作参考手册。
但注意:这个“快照”是**语句级还是事务级**?InnoDB 是**事务级快照**——即第一次 SELECT 才建立快照,不是 START TRANSACTION 就建。不过在 RR 级别下,只要没执行过 SELECT,第一次读就会固定快照,之后都一致。
容易踩的坑:
- 如果误设成
READ COMMITTED(如 PostgreSQL 默认),就一定会出现不可重复读 -
SELECT ... FOR UPDATE或LOCK 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 字段)。
真正容易被忽略的一点:**应用层重试 + 补偿逻辑,比死磕隔离级别更常见也更可靠**——尤其在微服务跨库场景,数据库隔离级别管不到服务间状态。









