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

来源:这里教程网 时间:2026-02-28 20:52:33 作者:

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

不可重复读是指:**同一个事务中,两次执行完全相同的

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
都基于这个快照读,不受其他事务已提交修改的影响。

但注意:这个“快照”是**语句级还是事务级**?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
字段)。

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

相关推荐