行锁卡表主因是未走索引导致全表扫描并加大量X锁或Next-Key锁,效果等同锁表;SELECT FOR UPDATE单靠查锁不能防超卖,须配合唯一索引查询、应用层校验与原子更新。

行锁为什么有时像表锁一样卡住整个表?
不是“升级”为表锁,而是没走索引时 InnoDB 被迫扫描全表,并对每条扫描到的记录加 X 锁(或 Next-Key Lock),锁数量爆炸,效果等同于锁表——尤其在高并发 UPDATE 场景下,极易引发大面积阻塞。
- 典型诱因:
WHERE条件触发隐式类型转换(如VARCHAR字段传入数字)、使用函数(YEAR(create_time) = 2024)、LIKE '%abc'、字段 COLLATION 不一致 - 验证方式:先跑
EXPLAIN SELECT ...,确认type是ref/range级别,且key显示用了哪个索引;再查performance_schema.data_locks观察LOCK_DATA是否批量出现 - 紧急缓解:避免直接改 SQL,可临时用主键分批更新(
UPDATE t SET x=1 WHERE id IN (1001,1002,...)),或加索引(注意 DDL 会持MDL写锁,可能阻塞线上查询)
SELECT FOR UPDATE 为什么扣库存还会超卖?
单靠 SELECT ... FOR UPDATE 并不能防止超卖——它只保证“查的时候锁住”,但应用层拿到结果后若未立即判断并原子执行更新,两个事务仍可能同时读到 count = 1,然后都执行 UPDATE ... SET count = count - 1,最终变成 -1。
- 正确姿势:必须用唯一索引(如
sku_id)查,SELECT count FROM stock WHERE sku_id = 'ABC123' FOR UPDATE,应用层严格判断返回值 ≥ 1 后再发 UPDATE - 严禁在
FOR UPDATE查询里JOIN大表,否则锁范围会扩散到被 JOIN 表的匹配行,甚至触发间隙锁蔓延 - 务必设置
innodb_lock_wait_timeout(默认 50 秒),并在代码中捕获Lock wait timeout exceeded错误做重试或降级
全局锁 FLUSH TABLES WITH READ LOCK 为什么备份还要慎用?
它确实能拿到一致性备份,但代价是整个实例只读——主库上用等于业务停摆;从库上用则导致 binlog 回放中断,主从延迟飙升。更隐蔽的风险是:锁期间若有长事务未结束,FTWRL 本身会被阻塞住,而它又会阻塞后续所有 DML,形成连锁等待。
- 替代方案优先考虑
mysqldump --single-transaction(InnoDB 表适用),它利用 MVCC 快照,不加全局锁也能获得逻辑一致视图 - 真要上
FTWRL,必须先查information_schema.INNODB_TRX确认无活跃长事务,再快速执行备份,立刻UNLOCK TABLES - 注意:即使加了全局锁,DDL 操作(如
ALTER TABLE)仍可能被阻塞在MDL阶段,反过来卡住FTWRL获取,这是容易忽略的死锁前兆
间隙锁(Gap Lock)到底锁了什么?
间隙锁不锁记录,只锁索引项之间的“空隙”。比如主键有 1, 5, 9,执行 SELECT * FROM t WHERE id BETWEEN 3 AND 7 FOR UPDATE,InnoDB 实际锁住的是 (1,5) 和 (5,9) 这两个间隙,阻止其他事务插入 id=3、id=4、id=6、id=7 等任意值——这是 RR 隔离级别防幻读的核心机制。
- 它只在
REPEATABLE READ及以上隔离级别生效;READ COMMITTED下间隙锁会被禁用,但幻读风险上升 - 唯一索引的等值查询(
WHERE id = 5)不会加间隙锁,只加 Record Lock;但范围查询(WHERE id > 5)或非唯一索引查询一定带间隙锁 - 线上排查锁冲突时,
information_schema.INNODB_LOCKS的LOCK_MODE字段若显示RECORD_GAP或NEXT_KEY,就说明间隙锁已介入,需重点检查 WHERE 条件是否过度宽泛
实际调优中最容易被绕开的点,是把“锁住了哪几行”当成唯一关注目标——而真正拖垮性能的,往往是那些看不见的间隙、被连带锁住的无关索引分支、以及事务提交前一直悬着的两阶段锁。











