mysql事务中如何避免不可重复读_mysql隔离策略实践

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

什么是不可重复读,它在 MySQL 里怎么表现

不可重复读是指:同一个事务中,两次

SELECT
同一条记录,结果不一致(被其他事务的
UPDATE
DELETE
修改了)。这不是幻读(幻读是查出新插入的行),也不是脏读(脏读是读到未提交的修改)。

典型复现场景:

REPEATABLE READ
隔离级别下,事务 A 查询某条订单状态为
'pending'
,事务 B 提交更新为
'shipped'
,事务 A 再查,发现值变了——这说明当前隔离级别没拦住不可重复读?错,其实是你用了
SELECT ... FOR UPDATE
SELECT ... LOCK IN SHARE MODE
以外的普通查询,而 MySQL 的
REPEATABLE READ
默认靠 MVCC 快照读实现,本该避免不可重复读。但如果你显式加了锁、或用了
READ COMMITTED
,就可能触发。

MySQL 默认的 REPEATABLE READ 真的能防不可重复读吗

能,但仅限于快照读(即普通

SELECT
)。它的实现依赖的是事务启动时创建的一致性视图(consistent read view),后续所有普通
SELECT
都基于这个快照,不会看到其他事务的已提交修改。

REPEATABLE READ
下,两次普通
SELECT id FROM orders WHERE order_id = 123
一定返回相同结果,不管其他事务是否已提交
UPDATE
但一旦用了加锁读(如
SELECT ... FOR UPDATE
),就会读最新已提交版本,并加行锁——此时就可能“重复读不一致”
READ COMMITTED
每次
SELECT
都新建快照,所以必然出现不可重复读;这是它的设计行为,不是 bug
MySQL 8.0+ 的
READ COMMITTED
在加锁读时还会用“间隙锁降级”,进一步放大不一致风险

想彻底避免不可重复读,该选哪个隔离级别和读法

如果业务逻辑要求“同一事务内多次读必须严格一致”,且不能接受锁等待或死锁,优先用

REPEATABLE READ
+ 普通
SELECT
。若必须加锁(比如要防止并发修改),就需配合应用层逻辑或升级到
SERIALIZABLE
——但代价是性能陡降、锁范围扩大。

别在
REPEATABLE READ
下混用快照读和加锁读:比如先
SELECT
判断状态,再
SELECT ... FOR UPDATE
修改,中间可能被篡改
真需要强一致性读写,把判断和更新合并成原子语句,例如:
UPDATE orders SET status = 'shipped' WHERE order_id = 123 AND status = 'pending'
SERIALIZABLE
会让所有普通
SELECT
隐式转成
SELECT ... LOCK IN SHARE MODE
,容易导致锁冲突,线上慎用
监控
Innodb_row_lock_waits
slow_query_log
中带
FOR UPDATE
的语句,它们往往是不可重复读问题的放大器

实战中最容易被忽略的三个点

很多不可重复读问题其实不出在隔离级别本身,而出在开发习惯和框架行为上。

ORM(如 MyBatis、Hibernate)默认开启二级缓存或 Session 缓存,可能掩盖真实数据库状态,误以为“没变”,实则数据库早已被改——关掉缓存或显式
clearCache()
测试
连接池(如 HikariCP)配置了
connection-init-sql
自动执行
SET SESSION TRANSACTION ISOLATION LEVEL ...
,但某些中间件(如 ShardingSphere)会覆盖它,导致实际隔离级别与预期不符
autocommit=1
时,每个
SELECT
都是独立事务,根本谈不上“重复读”——检查你的连接是否意外处于自动提交模式
MySQL 的不可重复读控制,本质是快照读与加锁读的选择权衡。真正难的不是设对隔离级别,而是让每一行 SQL 的读意图(我要快照?还是要锁?)和事务边界(是否跨 HTTP 请求?是否被框架拆开?)完全对齐。

相关推荐