临键锁解决什么问题
在 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 会把全表记录与间隙都锁住,此时任何写入都可能排队,影响面接近锁表,必须优先补索引。