InnoDB从不主动将行锁升级为表锁,所谓“锁升级”实为WHERE条件未走索引导致全表扫描、逐行加行锁(含间隙锁)的误判;真表锁仅来自LOCK TABLES、DDL或MyISAM引擎。

根本不用“修复”——InnoDB 从不主动把行锁升级为表锁,所谓“锁升级”是误判。真正的问题是:WHERE 条件没走索引,导致全表扫描,进而对每行都加行锁,锁了 5000+ 行后看起来像卡死的表锁。
为什么 UPDATE 或 SELECT FOR UPDATE 会“锁表”?
这不是锁类型变了,而是锁的数量爆炸了。InnoDB 在 REPEATABLE READ 隔离级别下,为防止幻读和不可重复读,会对所有扫描过的聚集索引记录 + 间隙都加锁(Next-key Lock)。没索引 → 全表扫描 → 锁全部 10 万行 → 等效于表锁。
-
EXPLAIN显示type: ALL或key: NULL,就是典型信号 - 模糊查询写成
WHERE name LIKE '%admin%',前置通配符必然丢索引 - 隐式类型转换:字段是
VARCHAR,却写WHERE phone = 138,MySQL 自动转成数字比较,索引失效 - 联合索引顺序错:建了
(name, city),却只查WHERE city = 'SZ',无法命中
怎么确认当前锁是真表锁还是“假表锁”?
别信 SHOW ENGINE INNODB STATUS\G 里模糊的 “HOLDS THE LOCK(S)” —— 它只报最近事务,漏掉静默持有者。MySQL 8.0+ 唯一可靠方式是查 performance_schema.data_locks:
SELECT LOCK_TYPE, LOCK_MODE, INDEX_NAME FROM performance_schema.data_locks WHERE OBJECT_SCHEMA = 'your_db' AND OBJECT_NAME = 'your_table';
LOCK_TYPE = 'TABLE' 才是真表锁(比如 LOCK TABLES t WRITE 或 ALTER TABLE);LOCK_TYPE = 'RECORD' 就是行锁(含间隙锁),哪怕锁了上万行。
- 配合
performance_schema.data_lock_waits查阻塞链:SELECT * FROM performance_schema.data_lock_waits; -
information_schema.INNODB_LOCKS和INNODB_LOCK_WAITS在 MySQL 8.0 已废弃,查不到完整信息
如何让语句真正走索引、避免“伪表锁”?
核心就一条:让优化器别选错执行计划。不是加更多锁,而是让锁尽可能窄、尽可能少。
- 模糊查询必须用前缀匹配?改用
FULLTEXT索引,或把搜索下沉到Elasticsearch - 批量操作超 5000 行?拆成
DELETE FROM log WHERE ts < '2024-01-01' LIMIT 1000循环执行 - 字段有索引但没生效?先
EXPLAIN,再检查是否隐式转换、函数包裹(WHERE DATE(created_at) = '2024-01-01')、或OR条件破坏索引合并 - 联合索引要覆盖查询:如果常查
WHERE city = ? AND status = ?,建INDEX idx_city_status (city, status),而非单列索引
最容易被忽略的是:锁范围不只由 WHERE 决定,还受隔离级别和扫描路径影响。哪怕加了索引,WHERE id > 100 在 REPEATABLE READ 下也会锁住间隙,而 READ COMMITTED 可禁用间隙锁——但代价是可能读到幻行。权衡点不在锁本身,而在业务能否容忍。


















