MySQL行锁不会升级为表锁,所谓“锁表”实为WHERE条件未走索引导致全表扫描并逐行加锁(RR下为Next-Key Lock),或显式/隐式触发真表锁(如LOCK TABLES、DDL、MyISAM)。

MySQL 行锁不会“升级”为表锁,但某些查询会让它看起来像锁了整张表——根本原因是锁的粒度没变,只是数量爆炸了,或者引擎干脆绕过行锁机制直接上表锁。
WHERE 条件没走索引:全表扫描 + 逐行加锁 = 假表锁
这是最常见、也最容易被误判为“锁升级”的场景。InnoDB 的行锁必须依赖索引定位记录;一旦优化器选不到索引,就会全表扫描,对每行都加 X 锁(RR 级别下是 Next-Key Lock),效果等同于锁表。
-
EXPLAIN显示type: ALL或key: NULL,基本可确认没走索引 - 模糊查询写成
WHERE name LIKE '%admin%',且name无索引 → 全扫 + 每行加锁 - 联合索引顺序错,比如建了
(name, city)却只查WHERE city = 'SZ'→ 索引失效 - 隐式类型转换:
phone是VARCHAR,却写WHERE phone = 138→ MySQL 自动转成数字比较,索引失效
隔离级别为 REPEATABLE READ:临键锁扩大范围,间隙也被锁死
RR 是 MySQL 默认隔离级别,它用 Next-Key Lock(行锁 + 间隙锁)防幻读。哪怕你只查一行,只要条件不精确命中唯一索引,InnoDB 就可能锁住一大片索引区间。
- 主键或唯一索引的等值查询(如
WHERE id = 10)只锁具体记录,不锁间隙 - 普通索引等值查询(如
WHERE idx_col = 5)会锁(3,5]和(5,8]这类区间,新插入可能被阻塞 - 范围查询如
WHERE order_no > 100 FOR UPDATE在 RR 下会锁住整个后缀区间,极易造成“锁一大片” - 如果业务能接受不可重复读,把隔离级别设为
READ-COMMITTED,能显著减少间隙锁开销
真表锁:和行锁机制完全解耦,别被日志骗了
这类不是“退化”,而是明明白白拿了表级锁。MySQL 5.7 和 8.0 行为一致,但 8.0+ 提供了唯一可靠的观测入口——performance_schema.data_locks。
-
LOCK TABLES t WRITE:会话级独占锁,后续所有 DML 都被拦,SELECT FOR UPDATE直接报ERROR 1100 -
ALTER TABLE/TRUNCATE TABLE:由 Server 层 MDL 控制,阻塞所有并发 DML,效果等同表锁 - MyISAM 表任意写操作:该引擎压根不支持行锁,
UPDATE就是表锁 - 查锁类型别信
SHOW ENGINE INNODB STATUS\G—— 它只显示最近事务,漏掉静默持有者;用SELECT LOCK_TYPE, LOCK_MODE FROM performance_schema.data_locks WHERE OBJECT_NAME = 'your_table',LOCK_TYPE = 'TABLE'才是真表锁
怎么快速验证当前是不是真表锁?
别靠感觉,用数据说话。重点盯三个地方:
- 查
information_schema.INNODB_TRX:如果TRX_ROWS_LOCKED异常高(几万甚至几十万),而业务只改 1~2 行,基本就是锁范围失控 - 看
SHOW ENGINE INNODB STATUS\G中的heap size:超过 1MB 说明锁结构膨胀严重,InnoDB 已倾向简化处理 - 执行
SHOW GLOBAL STATUS LIKE 'table_locks%':若table_locks_waited显著上升,尤其伴随table_locks_immediate下降,说明有非 MyISAM 引擎语句正遭遇表级阻塞
真正难缠的从来不是锁多,而是锁乱——比如多个事务无序更新主键,形成等待链,死锁检测频繁触发回滚。这时候加索引、调隔离级别、拆批量操作,比纠结“锁升没升级”管用得多。


















