innodb行锁本质是索引锁,无主键且无可用索引时,update需全表扫描并逐行加锁,效果等同于表锁,导致高并发阻塞。

无主键表在UPDATE时无法走行锁,InnoDB被迫升级为表锁
InnoDB 的行锁依赖于聚簇索引(通常是主键)定位具体行。当表没有主键时,InnoDB 会尝试用唯一非空索引替代;若连这样的索引也没有,它就只能退化为使用隐藏的 row_id 作为聚簇索引——但这个 row_id 是全局递增、非唯一、不暴露给用户的,且无法用于精确查找。
结果就是:任何 UPDATE 或 DELETE 语句只要没命中可用索引,InnoDB 就无法判断“哪几行要改”,只能全表扫描 + 对所有扫描到的行逐个加锁。而高并发下多个会话同时扫描同一张无主键表,极易触发锁冲突,最终表现为整张表被阻塞。
为什么“没索引”会让UPDATE变成全表扫描
执行 UPDATE t SET a = 1 WHERE b = 100 时,InnoDB 必须先找到满足 b = 100 的所有行。如果 b 列没有索引,就只能遍历整个表(即全表扫描)。这个过程不是“只读扫描”,而是边扫边加锁——对每行都尝试加 X 锁(排他锁),直到确认是否符合 WHERE 条件。
- 即使最终只更新 1 行,扫描过程中已对成千上万行加了临时锁
- 其他并发事务想更新任意一行(哪怕和这行完全无关),都可能因等待这些临时锁而卡住
- MySQL 5.7+ 中,这种场景下
INFORMATION_SCHEMA.INNODB_TRX里能看到事务状态为updating or deleting,但TRX_ROWS_LOCKED值异常高
show engine innodb status 能看到什么线索
运行 SHOW ENGINE INNODB STATUS\G 后,在 TRANSACTIONS 部分重点关注:
-
lock struct(s)数量远超实际修改行数(比如显示 128000 个锁,但业务逻辑只该改 1 行) -
TABLE LOCK出现在事务锁信息中(说明已降级为表级保护) -
mysql tables in use和locked in table cache显示同一张表被多次引用
再配合 SELECT * FROM information_schema.INNODB_LOCK_WAITS;,常会发现多个事务在等同一个 LOCK_TRX_ID ——那个持有海量行锁却迟迟不提交的事务,大概率正卡在无主键表的 UPDATE 上。
修复动作比想象中更简单,但容易漏掉隐含依赖
加主键不是加完就完事。要注意:
- 必须是
NOT NULL+UNIQUE(或直接PRIMARY KEY),仅UNIQUE但允许NULL不生效 - 如果业务代码里有
INSERT ... ON DUPLICATE KEY UPDATE,加主键后行为会变,需同步检查 - 历史数据存在重复值?
ALTER TABLE t ADD PRIMARY KEY (id);会直接报错,得先用GROUP BY清洗 - 大表加主键会锁表(尤其 MySQL 5.6 及以前),建议在低峰期用
ALGORITHM=INPLACE(MySQL 5.6+)或 pt-online-schema-change
真正难的不是加主键本身,而是确认这张表到底有没有被当成“临时容器”在用——比如日志表、中间计算表、ETL 缓冲表。这类表常被忽略索引设计,但又意外承载了高并发写入,一出问题就是雪崩式阻塞。











