InnoDB从不主动升级行锁为表锁,所谓“升级”实为全表扫描时逐行加next-key lock导致等效表锁;根本原因是WHERE条件未走索引,触发REPEATABLE READ下的间隙锁与记录锁组合覆盖全表。

UPDATE走全表扫描时,InnoDB根本没“升级”锁
它只是老老实实给每一行加行锁——扫10万行,就加10万个next-key lock。所谓“锁升级”,其实是人眼看到其他事务全卡住,误以为MySQL主动把行锁换成了表锁。InnoDB从不主动做这种事,LOCK TABLES、DDL或MyISAM才真用表锁。
为什么全表扫描会导致等效表锁?
关键在隔离级别和锁范围:InnoDB在REPEATABLE READ下为防幻读,对所有扫描过的聚集索引记录+间隙都加锁。没索引 → 全表扫描 → 锁住全部数据页 + 大量间隙 → 其他事务一碰WHERE条件或INSERT新行就阻塞。常见现象包括:
-
SHOW PROCESSLIST里一堆Updating状态,持续几分钟不退 -
INFORMATION_SCHEMA.INNODB_TRX中trx_rows_locked高达几十万 - 其他查询报
Lock wait timeout exceeded或卡在Waiting for table metadata lock
怎么确认是不是全表扫描惹的祸?
别猜,直接看执行计划。对对应UPDATE的SELECT语句跑EXPLAIN:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
-
type字段是ALL或index→ 基本确定全表/全索引扫描 -
key为空,或显示的不是你建的索引名 → 没命中索引 - 隐式转换很隐蔽:
user_id是BIGINT,却传'123';phone是VARCHAR,却写WHERE phone = 13800138000 - 函数操作直接废索引:
WHERE DATE(created_at) = '2026-10-01'→ 应改写为created_at >= '2026-10-01' AND created_at
分批UPDATE不能靠LIMIT,必须用主键游标
LIMIT只限制修改行数,不减少扫描量。哪怕UPDATE ... WHERE status = 'pending' LIMIT 1000,只要status没索引,仍会全表扫描+锁满100万行,最后只改前1000条——锁早加完了。
- 正确做法:用有索引的单调字段(如
id)做范围切片,例如WHERE id > 100000 AND id - 每批必须显式
BEGIN+COMMIT,锁才真正释放 - 下一批起点必须是上一批
MAX(id),不能靠OFFSET或预估总页数 - 批次大小建议1000–5000:太小事务开销高;太大单次锁持有时间仍长
最常被忽略的一点:查锁源永远比调SQL优先。一条UPDATE卡住,90%不是它自己有问题,而是前面某个长事务或DDL正拿着MDL不放。先看performance_schema.data_locks和INNODB_TRX,再动索引和分批逻辑。

















