全表扫描(type为all/index)、临时表/文件排序(using temporary/filesort)、驱动表选择错误、子查询重写失效及索引过滤性差(filtered过低)是mysql cpu飙升的五大主因。

EXPLAIN 显示 type 为 ALL 或 index 就意味着全表扫描
这通常是你 CPU 暴涨的直接原因:MySQL 对每张参与 JOIN 的表都得扫一遍,数据量一上去,CPU 就在做纯体力活。尤其当 EXPLAIN 的 rows 列显示几十万甚至百万级时,基本可以确定是瓶颈所在。
实操建议:
- 立刻检查对应表的
WHERE条件字段是否建了索引——没索引就等于没路标,MySQL 只能硬扫 -
JOIN条件字段(比如ON t1.user_id = t2.id)两边的列类型、字符集、是否允许NULL必须严格一致,否则索引失效 - 别信“这个字段有索引”,用
SHOW INDEX FROM table_name确认索引实际生效的是哪几列,复合索引顺序错一位,效果归零
EXPLAIN 中 Extra 出现 Using temporary 和 Using filesort
这两个标志说明 MySQL 正在内存里建临时表、或把结果集拉出来排序——不是查得慢,是边查边造中间结构,CPU 和内存双吃紧。常见于 GROUP BY、ORDER BY 涉及非驱动表字段,或者 SELECT * 拉了太多冗余列。
实操建议:
- 把
ORDER BY字段尽量限制在驱动表(EXPLAIN中id最小的那张)上,否则大概率触发临时表 - 避免在
JOIN后对非索引字段GROUP BY,比如GROUP BY t2.name却没给t2.name建索引 -
SELECT只写真正需要的字段,SELECT *会拖慢排序和临时表构建速度
JOIN 顺序被优化器搞错了,驱动表选成大表
MySQL 的 JOIN 是嵌套循环,小表做驱动表才高效。但优化器有时会误判行数(比如统计信息过期),选了个百万级表当驱动表,后面每行都去匹配另一张表,CPU 直接拉满。
实操建议:
- 执行
ANALYZE TABLE table_name更新统计信息,让优化器“看清”真实数据分布 - 用
STRAIGHT_JOIN强制指定 JOIN 顺序(只在确认优化器确实错了时用),例如:SELECT STRAIGHT_JOIN ... FROM small_table JOIN big_table ON ... - 观察
EXPLAIN的id列:数字越小越先执行,确保它对应的是你预期的小表
子查询被重写成 JOIN 后反而更慢
MySQL 5.6+ 会自动把某些 IN 子查询转成 SEMI-JOIN,听起来好,但如果子查询结果集大、又没合适索引,这种“自动优化”反而让执行计划崩坏,EXPLAIN 里可能看到 select_type 变成 DEPENDENT SUBQUERY 或 MATERIALIZED,但 rows 爆增。
实操建议:
- 用
SELECT /*+ NO_SEMIJOIN() */ ...(MySQL 8.0+)或改写为显式LEFT JOIN ... IS NOT NULL来绕过自动重写 - 给子查询里的关联字段单独建索引,比依赖外层 JOIN 的索引更可靠
- 对比
EXPLAIN FORMAT=JSON输出,看query_block下有没有materialized_from_subquery,有就说明被物化了,得盯住它的rows和filtered
最麻烦的不是看不懂 EXPLAIN,而是它看起来“都合理”——索引用了、type 是 ref、没临时表,但 CPU 还是高。这时候得看 filtered 列是不是低得离谱(比如 1%),说明索引过滤性极差,实际还是要扫大量行。这个值没法直接优化,只能换条件、加覆盖索引,或者拆查询。











