mysql间隙锁是什么_mysql锁原理解析

11次阅读

间隙锁是InnoDB在REPEATABLE READ级别下锁定索引中两值间空档(如(5,10))以防止幻读的机制,仅对范围查询或非唯一索引等值未命中时触发,不阻塞其他间隙锁但会阻塞插入。

mysql间隙锁是什么_mysql锁原理解析

间隙锁是啥?一句话说清

间隙锁(Gap Lock)不是锁某一行,而是锁住索引中两个值之间的“空档”——比如现有记录 id = 5id = 10,那 (5, 10) 这个开区间就被锁住了,别人不能往里插 id = 7id = 8 的新行。它只在 REPEATABLE READ 隔离级别下生效,核心目的就一个:堵住幻读的漏洞

什么操作会触发间隙锁?

不是所有 select ... for UPDATE 都会加间隙锁,得看查询条件和索引结构:

  • 范围查询必触发:比如 SELECT * FROM t WHERE id BETWEEN 10 AND 20 FOR UPDATE,会锁住所有命中范围的记录 + 它们之间的间隙 + 边界外的间隙(如 (5, 10)(10, 20)(20, 25)
  • 等值查询但没命中,且字段有**非唯一索引**:比如 WHERE c = 7,而表里 c 字段只有 510,那就会锁 (5, 10)
  • 唯一索引等值查询且记录存在 → 只加 RECORD LOCK(行锁),不加间隙锁
  • READ COMMITTED 级别下,间隙锁被完全禁用,所以不会出现因间隙锁导致的插入阻塞

怎么确认自己被间隙锁卡住了?

常见现象是:事务 A 执行了带锁的范围查询,事务 B 突然发现 INSERT 卡住不动,SHOW PROCEsslIST 显示 updateinsert 状态为 Locked,但查不到显式锁表语句。这时可以:

  • 查当前锁状态:
    SELECT * FROM performance_schema.data_locks;

    关注 LOCK_MODE 列:若看到 X,GAP 就是间隙锁,X,REC_NOT_GAP 是纯行锁,X(无后缀)通常是 Next-Key Lock

  • 检查隔离级别:
    SELECT @@transaction_isolation;

    必须是 REPEATABLE-READ 才可能触发

  • 看执行计划是否走了索引:
    EXPLaiN SELECT * FROM t WHERE c BETWEEN 10 AND 20 FOR UPDATE;

    如果 typeALL(全表扫描),InnoDB 可能退化为锁整个索引范围,甚至升级成表锁

间隙锁容易踩的坑

它不冲突、不互斥,但副作用很强:

  • 多个事务对同一间隙加 Gap Lock 不会阻塞彼此,但只要有一个事务在该间隙内尝试 INSERT,就会被全部卡住 —— 表面看没锁竞争,实际是“静默阻塞”
  • 主键自增列上做范围查询,间隙可能延伸到 (max_id, +∞),导致后续所有 INSERT 都被拦住,尤其在分页写入或批量导入时很隐蔽
  • 业务误以为“没查到数据=没锁”,结果在 WHERE status = 'pending'(非唯一索引)后直接 INSERT,却因间隙锁失败,日志里只报超时,不报死锁
  • 升级 mysql 版本后行为可能变化:8.0.18+ 对唯一索引的等值查询优化更强,但非唯一索引的范围扫描加锁逻辑更激进,建议上线前用 data_locks 实测

真正难的不是理解间隙锁,而是它总在你没意识到的地方悄悄起作用——尤其是当你的查询看似“只读”,却用了 FOR UPDATE 或走的是非唯一索引时。

text=ZqhQzanResources