生产环境MySQL全表扫描慢事务定位方法
作者:技术拾光时间:2026-07-14 08:28:06浏览:401
通过EXPLAIN的type字段为ALL、key为NULL、rows接近总行数等信号可判断全表扫描。使用SHOWPROCESSLIST或INFORMATION_SCHEMA.PROCESSLIST快速定位慢查询。索引失效常见于函数调用、隐式类型转换、左模糊查询及联合索引顺序错误。建议优先覆盖WHERE条件加索引,并用pt-online-schema-chan
判断一条SQL是否在走全表扫描,最直接的办法就是看`EXPLAIN`输出里的`type`字段。只要它显示为`ALL`,那就是铁定没走索引——不是“可能”,是实锤。除此之外,还有几个关键信号值得一并盯住:`key`字段为`NULL`、`rows`数量接近总行数、`Extra`里出现`Using where`但没有`Using index`。这些信号组合在一起,基本可以断定MySQL正在硬着头皮扫全表。
## 怎么快速揪出正在全表扫描的 SQL?
别等到慢查询日志积累半天才去翻。先连上生产库,直接用`SHOW PROCESSLIST`看当前活跃会话里哪些在扫大表:
- `State`是`Sending data`且`Time`超过几秒,大概率在扫;
- `Info`字段里带`SELECT` + 大量`WHERE`条件但没`ORDER BY`或`LIMIT`,尤其要留意有没有函数调用(比如`DATE(created_at)`这种);
- 配合`INFORMATION_SCHEMA.PROCESSLIST`查得更全:`SELECT ID, INFO, TIME FROM INFORMATION_SCHEMA.PROCESSLIST WHERE COMMAND = 'Query' AND TIME > 3 AND INFO LIKE '%SELECT%';`
## EXPLAIN 里哪些信号说明它在全表扫描?
`EXPLAIN`不是看热闹,重点盯三个字段:
- `type`是`ALL`:铁证,没走任何索引;
- `key`是`NULL`:即使`possible_keys`有值,`key`为空也等于没用;
- `rows`数字极大(比如超过50000):哪怕`type`是`index`,扫整张索引树也等价于全表扫描;
- `Extra`出现`Using where`但没`Using index`:说明查了索引还得回表,如果`rows`很大,实际开销接近全表。
## 为什么明明建了索引还全表扫描?常见失效场景
索引存在 ≠ 被用。这几个写法会让MySQL直接放弃索引:
- WHERE中对字段用了函数:`WHERE YEAR(create_time) = 2026` → 改成`WHERE create_time >= '2026-01-01' AND create_time < '2027-01-01'`;
- 隐式类型转换:`WHERE user_id = '123'`(`user_id`是INT)→ 字符串强制转数字,索引失效;
- LIKE左模糊:`WHERE name LIKE '%abc'` → 没法走B+树前缀匹配;
- 联合索引顺序错:`INDEX(a,b,c)`,但查询只用`WHERE c = 1` → 不满足最左前缀,直接跳过。
## 线上不敢随便改 SQL?先加索引试试
如果确认是缺失索引导致的`ALL`,加索引比重写业务逻辑快得多,但要注意:
- 优先覆盖`WHERE`条件字段,再追加`ORDER BY`和`SELECT`字段(做成覆盖索引);
- 避免单列索引堆砌,比如`WHERE a=1 AND b=2`,建`INDEX(a,b)`比分别建两个好;
- 加索引前用`pt-online-schema-change`或MySQL 8.0+的`ALGORITHM=INSTANT`,别锁表;
- 加完立刻`EXPLAIN`验证,别信“应该能走”。
全表扫描本身不致命,致命的是它出现在高频接口或大表上。真正麻烦的是那些`rows`显示几千、但实际执行扫了百万行的SQL——`EXPLAIN`的预估不准时,得靠`SHOW PROFILE`或性能模式(`performance_schema`)看真实I/O开销。
## 怎么快速揪出正在全表扫描的 SQL?
别等到慢查询日志积累半天才去翻。先连上生产库,直接用`SHOW PROCESSLIST`看当前活跃会话里哪些在扫大表:
- `State`是`Sending data`且`Time`超过几秒,大概率在扫;
- `Info`字段里带`SELECT` + 大量`WHERE`条件但没`ORDER BY`或`LIMIT`,尤其要留意有没有函数调用(比如`DATE(created_at)`这种);
- 配合`INFORMATION_SCHEMA.PROCESSLIST`查得更全:`SELECT ID, INFO, TIME FROM INFORMATION_SCHEMA.PROCESSLIST WHERE COMMAND = 'Query' AND TIME > 3 AND INFO LIKE '%SELECT%';`
## EXPLAIN 里哪些信号说明它在全表扫描?
`EXPLAIN`不是看热闹,重点盯三个字段:
- `type`是`ALL`:铁证,没走任何索引;
- `key`是`NULL`:即使`possible_keys`有值,`key`为空也等于没用;
- `rows`数字极大(比如超过50000):哪怕`type`是`index`,扫整张索引树也等价于全表扫描;
- `Extra`出现`Using where`但没`Using index`:说明查了索引还得回表,如果`rows`很大,实际开销接近全表。
## 为什么明明建了索引还全表扫描?常见失效场景
索引存在 ≠ 被用。这几个写法会让MySQL直接放弃索引:
- WHERE中对字段用了函数:`WHERE YEAR(create_time) = 2026` → 改成`WHERE create_time >= '2026-01-01' AND create_time < '2027-01-01'`;
- 隐式类型转换:`WHERE user_id = '123'`(`user_id`是INT)→ 字符串强制转数字,索引失效;
- LIKE左模糊:`WHERE name LIKE '%abc'` → 没法走B+树前缀匹配;
- 联合索引顺序错:`INDEX(a,b,c)`,但查询只用`WHERE c = 1` → 不满足最左前缀,直接跳过。
## 线上不敢随便改 SQL?先加索引试试
如果确认是缺失索引导致的`ALL`,加索引比重写业务逻辑快得多,但要注意:
- 优先覆盖`WHERE`条件字段,再追加`ORDER BY`和`SELECT`字段(做成覆盖索引);
- 避免单列索引堆砌,比如`WHERE a=1 AND b=2`,建`INDEX(a,b)`比分别建两个好;
- 加索引前用`pt-online-schema-change`或MySQL 8.0+的`ALGORITHM=INSTANT`,别锁表;
- 加完立刻`EXPLAIN`验证,别信“应该能走”。
全表扫描本身不致命,致命的是它出现在高频接口或大表上。真正麻烦的是那些`rows`显示几千、但实际执行扫了百万行的SQL——`EXPLAIN`的预估不准时,得靠`SHOW PROFILE`或性能模式(`performance_schema`)看真实I/O开销。