innodb 行锁会退化为表锁:当无索引字段+查询条件时触发全表扫描并加表级x锁;间隙锁在rr级别下阻塞插入,rc级别不启用;真实锁等待需通过show engine innodb status分析transactions和lock wait段落。

表级锁 vs 行级锁:什么时候会退化成表锁
MySQL 的锁粒度直接影响并发能力,但实际中 InnoDB 并非总能用上行级锁。比如执行 UPDATE t SET a=1 WHERE b LIKE '%abc%' 时,若 b 列没有索引,优化器无法定位具体行,就会对全表加 X 锁(即表级写锁)。这种退化在慢查询日志里常表现为 Rows_examined 远大于 Rows_affected。
- 有主键或唯一索引的等值条件(
WHERE id = 123)→ 精确行锁 - 范围查询(
WHERE created_at > '2024-01-01')→ 可能锁住间隙(Gap Lock),甚至整个索引段 - 无索引字段 + 条件 → 全表扫描 + 表级锁(即使引擎是 InnoDB)
-
SELECT ... FOR UPDATE在 RC 隔离级别下不加 Gap Lock,但在 RR 下会,影响并发插入
间隙锁(Gap Lock)为何让“插入变慢”
间隙锁是 InnoDB 实现可重复读(RR)的关键机制,但它会锁住索引中不存在的“空档”。例如 id 是主键,当前有记录 (1),(5),(10),执行 SELECT * FROM t WHERE id BETWEEN 3 AND 7 FOR UPDATE,InnoDB 会锁住 (1,5) 和 (5,10) 这两个间隙 —— 此时 INSERT INTO t VALUES (4) 就会被阻塞。
- 间隙锁只存在于 RR 隔离级别;RC 下不启用,但幻读风险上升
- 唯一索引的等值查询(含主键)不会触发间隙锁,只锁匹配行
- 非唯一索引的等值查询仍可能锁间隙(如
name='alice',而 name 不唯一) - 用
SELECT ... LOCK IN SHARE MODE同样受间隙锁影响,不只是FOR UPDATE
如何用 SHOW ENGINE INNODB STATUS 查真实锁等待
仅看 PROCESSLIST 或慢日志不够,真正卡在哪、谁在等谁,得靠 InnoDB 自带的状态快照。执行后重点关注 TRANSACTIONS 和 LOCK WAIT 段落。
WebShop网上商店系统专注中小企业、个人的网上购物电子商务解决方案,淘宝商城系统用户/个人首选开店的购物系统!综合5500多用户的意见或建议,从功能上,界面美观上,安全性,易用性上等对网店系统进行了深度的优化,功能更加强大,界面模板可直接后台选择。WebShop网上商店系统特点:1 对于中小企业、个体、个人、店主和淘宝易趣等卖家,可利用WebShop快速建立购物网。2 源代码开放,利用WebS
SHOW ENGINE INNODB STATUS\G
- 找
---TRANSACTION [hex_id], ACTIVE [sec] sec行,确认事务是否长时间未提交 - 看到
lock_mode X locks rec but not gap waiting→ 正在等某一行锁释放 - 看到
lock_mode X locks gap before rec insert intention waiting→ 被间隙锁堵住插入 -
Trx has been waiting [n] sec for this lock超过 5 秒就该警惕了 - 注意
mysql tables in use [n], locked [m]中 locked > 1 可能是隐式锁升级
降低锁冲突的实操建议
锁不是越细越好,而是要和业务节奏对齐。盲目拆分事务或加索引反而引入新问题。
- 写操作尽量走主键或覆盖索引,避免回表 → 减少锁行数
- 批量更新改用
INSERT ... ON DUPLICATE KEY UPDATE替代先查后更,减少事务持有时间 - 高并发计数场景(如点赞数)别直接
UPDATE t SET cnt = cnt + 1,改用 Redis 计数+异步落库 - 长事务必须拆:比如导出报表,不要在一个事务里查 10 万行再统一更新,分页处理并及时提交
- 避免在事务中调用外部 HTTP 接口或 sleep(),锁会一直持有着
锁粒度和性能之间没有银弹,关键在理解每一行 SQL 落到索引树上的真实路径 —— 看执行计划只是起点,看锁状态才是真相。










