Next-Key Lock能防止幻读,因其是记录锁与间隙锁的组合,形成左开右闭区间(a,b],既锁定现有记录又封锁插入间隙,从而阻断其他事务在范围内插入新行。

Java 应用本身不直接操作 Next-Key Lock,它依赖底层 MySQL InnoDB 引擎在 REPEATABLE READ 隔离级别下自动启用该机制;Java 事务只需正确配置隔离级别并触发“当前读”,Next-Key Lock 就会由数据库自动加锁,从而防止幻读。
关键前提:必须是可重复读 + 当前读
Next-Key Lock 不会在所有查询中生效,只在满足以下两个条件时激活:
- 事务隔离级别设为 REPEATABLE READ(MySQL InnoDB 默认值,Java 中需显式指定)
- 执行的是当前读(Current Read)语句,如:
SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE、UPDATE、DELETE、INSERT ... ON DUPLICATE KEY UPDATE
普通 SELECT(快照读)靠 MVCC 实现一致性视图,不加锁,也不触发 Next-Key Lock。
Java 中如何触发 Next-Key Lock
以 Spring 声明式事务为例,需确保:
立即学习“Java免费学习笔记(深入)”;
- 使用
@Transactional(isolation = Isolation.REPEATABLE_READ)(注意:JDBC 驱动和 MySQL 服务端必须支持该级别) - 在事务方法内执行带锁的 SQL,例如 JdbcTemplate 执行:
update("UPDATE account SET balance = ? WHERE id = ? FOR UPDATE", newBalance, accountId) - 若用 MyBatis,Mapper XML 中写:
<select id="selectForUpdate" resultType="Account">SELECT * FROM account WHERE status = 1 FOR UPDATE</select>
此时 InnoDB 会根据查询条件(尤其是是否走索引、是否唯一)自动选择加 Record Lock、Gap Lock 或 Next-Key Lock。
为什么能防幻读?锁住“行+间隙”才是关键
假设表 order 主键为 id,现有数据 id = 1, 5, 10,执行:
SELECT * FROM order WHERE id BETWEEN 3 AND 8 FOR UPDATE;
InnoDB 实际锁定范围是:
-
Record Lock:命中
id=5这一行(防止修改) -
Gap Lock:锁定间隙
(1, 5)和(5, 10)(防止插入id=3,4,6,7,8,9等) - 合起来就是两个 Next-Key Lock:即
(1, 5]和(5, 10]
其他事务在此期间尝试 INSERT INTO order VALUES (4, ...) 会被阻塞,直到当前事务提交或回滚,从而彻底堵住幻读入口。
注意事项与常见误区
这些情况会让 Next-Key Lock 失效或退化:
- 没走索引:全表扫描时可能升级为表锁,或锁范围远超预期
-
等值查询唯一索引:如
SELECT ... WHERE id = 5 FOR UPDATE→ 只加 Record Lock(不锁间隙),但仍可防幻读,因唯一键插入冲突会报错 -
查询条件含非确定性函数(如
NOW()、RAND())可能导致优化器无法精确估算范围,影响锁粒度 - READ COMMITTED 级别下:InnoDB 不使用 Next-Key Lock,仅用 Record Lock,幻读风险重现
因此 Java 端不仅要设对隔离级别,还要确保 SQL 走索引、避免模糊条件,并配合业务逻辑做幂等或重试处理。


















