EXPLAIN显示type=ALL或key=NULL即确诊未走索引,InnoDB被迫全表扫描并逐行加X锁,效果等效表锁;常见原因包括隐式转换、函数操作、联合索引未用左列等。

EXPLAIN 显示 type=ALL 或 key=NULL 就是危险信号
这代表查询没走索引,InnoDB 必须逐行扫描聚簇索引,对每行都加 X,REC_NOT_GAP 锁(或 X,NEXT_KEY,取决于隔离级别)。效果上锁了整张表,但底层仍是行锁——只是数量太多,等效于表锁。
常见诱因包括:
-
WHERE name LIKE '%alice%'且name没索引 - 联合索引
(a,b),却只查WHERE b = 'x' -
phone是VARCHAR,却写WHERE phone = 138(隐式类型转换) -
WHERE DATE(create_time) = '2024-01-01'(函数导致索引失效)
TRX_ROWS_LOCKED 远大于 TRX_ROWS_MODIFIED 说明锁膨胀
执行完 UPDATE 或 DELETE 后不提交,查 information_schema.INNODB_TRX:
SELECT TRX_ID, TRX_ROWS_LOCKED, TRX_ROWS_MODIFIED FROM information_schema.INNODB_TRX WHERE TRX_STATE = 'RUNNING';
若 TRX_ROWS_LOCKED 是几十万、而 TRX_ROWS_MODIFIED 只有 1,基本可断定:语句本意只改一行,但因没索引,被迫锁了全部扫描行。
注意:TRX_ROWS_LOCKED 统计的是“加锁记录数”,不是“满足 WHERE 条件的行数”——全表扫描时,它等于扫描路径上的所有行数。
MySQL 8.0+ 看 performance_schema.data_locks 确认实际锁对象
在事务未提交时运行:
SELECT LOCK_TRX_ID, LOCK_MODE, LOCK_TYPE, LOCK_DATA FROM performance_schema.data_locks WHERE LOCK_TRX_ID IN (SELECT TRX_ID FROM information_schema.INNODB_TRX);
关键观察点:
- 若
LOCK_DATA出现大量不同主键值(如id=1、id=2、id=3…),说明是密集行锁 - 若
LOCK_MODE是X,REC_NOT_GAP且条目极多,就是全表扫描加锁的铁证 - 真正意义上的表锁(
LOCK_TYPE = TABLE)极少出现,除非用了LOCK TABLES
别依赖 EXPLAIN 的 rows 字段:它只是优化器估算值;哪怕显示 rows=5,只要 type=ALL,就表示这 5 行来自全表扫描路径,不是“命中索引后筛选出的 5 行”。
慢查询日志 + log_queries_not_using_indexes 能提前预警
开启配置:
log_queries_not_using_indexes = ONlong_query_time = 0
这样所有没走索引的 UPDATE/DELETE 都会被记入慢日志。配合应用层日志,能快速定位哪条业务 SQL 在生产环境反复触发锁膨胀。
但要注意:该配置也会记录没索引的 SELECT,需过滤出 DML 语句再分析。
最隐蔽的问题往往不在 SQL 写法本身,而在字段类型和条件写法之间的微妙错位——比如 user_id 是 BIGINT,前端传参却带了引号变成字符串,MySQL 自动转成数字时丢弃索引,锁就悄悄铺满全表。


















