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

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

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

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

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

典型场景:

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

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

MySQL 默认隔离级别是

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

注意以下关键点:

REPEATABLE READ
下,普通
SELECT
基于事务开启时的 Read View,不会看到其他事务后续的提交,因此能保证不可重复读不发生
SELECT ... FOR UPDATE
SELECT ... 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
下,只要用了
UPDATE
DELETE
或当前读,就不再受快照保护;而业务代码里往往没意识到自己触发了当前读。

相关推荐