首页/数据/数据库速查/临键锁与幻读防治(RR 语义)
性能与调优57 条

临键锁与幻读防治(RR 语义)

RR 默认用临键锁(记录+间隙)把范围条件锁成区间,防止其他事务插入新行(幻读);等值唯一命中时退化为记录锁。

数据库速查数据更新于 2026-10-13

临键锁解决什么问题

在 REPEATABLE READ 隔离级别下,InnoDB 做当前读(FOR UPDATE、UPDATE、DELETE)时默认使用临键锁:既锁住命中的记录,也锁住记录之间的间隙,从而阻止其他事务插入新行形成幻读。等值条件命中唯一索引时,临键锁会退化为记录锁,锁范围因此收窄。

基本信息

适用MySQL 5.7 / 8.0,默认隔离级别 REPEATABLE READ
锁范围命中记录 + 前方间隙(到下一个记录为止)
退化情况唯一索引等值命中时仅加记录锁
作用范围只在当前读时生效,快照读不加行锁

行为示例

行为示例
-- 表上有二级索引 (user_id),隔离级别为 REPEATABLE READ
BEGIN;
SELECT * FROM appdb.orders WHERE user_id = 100 FOR UPDATE;   # 命中记录加锁,相邻间隙一并锁定
-- 此时另一事务执行下面这条会被阻塞
--   INSERT INTO appdb.orders (user_id, amount) VALUES (100, 1);
SELECT * FROM appdb.orders WHERE amount = 500 FOR UPDATE;    # amount 无索引:等价于锁住全表记录与间隙
COMMIT;                                                       # 提交后间隙锁释放,插入方可继续

锁形态与触发条件

情形加锁形态影响
唯一索引等值命中记录锁只锁一行,影响面最小
非唯一索引等值命中记录锁 + 间隙锁同值的相邻区间不可插入
范围条件区间内记录与间隙范围外相邻业务插入也可能被挡
条件无索引全表记录与间隙近似表锁,插入风暴直接排队
READ COMMITTED 级别多数间隙锁被省略并发更高,但幻读需业务自行处理

典型使用场景

  • 排查「没人锁这一行,插入却被阻塞」的隐性锁范围问题
  • 库存扣减等场景确认 FOR UPDATE 是否锁住了预期之外的行
  • 从 RR 切到 RC 之前评估幻读与锁竞争的变化
  • 批量更新前检查条件列是否有索引,避免退化为全表间隙锁
  • 死锁复盘时确认两个事务的间隙锁是否交叉形成环路
间隙锁只在当前读中出现:普通的 SELECT 快照读不加行锁;同一条件下 FOR UPDATE 与普通 SELECT 的行为差异常被误判为参数问题。
无索引条件上的 FOR UPDATE 会把全表记录与间隙都锁住,此时任何写入都可能排队,影响面接近锁表,必须优先补索引。

易错点

  • 范围 FOR UPDATE 锁住的范围超出预期:相邻业务的插入被莫名阻塞,需用 data_locks 确认实际锁区间
  • 条件列没有索引就上 FOR UPDATE:全表间隙锁导致写入风暴下整库排队
  • 以为 RC 级别就完全没有间隙锁:外键检查与唯一键冲突检查等场景仍可能出现间隙锁
  • 长事务持有间隙锁:间隙锁在提交前一直有效,事务越久、被挡住的插入越多

数据来源与参考

  • MySQL 8.0 Reference Manual: Locking Reads
  • MySQL 8.0 Reference Manual: InnoDB Locking

相关工具

​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌