根本原因不是join本身,而是没索引的join触发全表扫描+嵌套循环匹配,导致cpu满载;explain中type=all或rows极大即为典型表现,需逐表检查on字段索引、复合索引最左前缀、类型一致性及驱动表选择。

根本原因不是JOIN本身,而是没索引的JOIN触发全表扫描 + 嵌套循环匹配,让CPU干纯体力活。
EXPLAIN里看到type=ALL或rows极大,基本就是CPU在硬扫
MySQL执行JOIN用的是嵌套循环(Nested-Loop),驱动表每取出一行,就要去被驱动表里逐行比对。如果被驱动表没走索引,type就变成ALL,rows显示几十万甚至百万——这不是“查得慢”,是CPU在内存里反复加载、比对、丢弃,每秒几万次操作直接拉满核心。
- 常见诱因:
ON u.name = o.user_name但u.name没索引,或类型不一致(比如一边是VARCHAR,一边传了INT导致隐式转换) - 复合索引失效:索引是
(status, user_id),但查询只写了WHERE user_id = 123,最左前缀没用上 - 别信“这张表有索引”,用
SHOW INDEX FROM users确认索引字段顺序和是否启用
Using temporary + Using filesort同时出现,CPU正在内存里造临时结构
当EXTRA列同时出现Using temporary和Using filesort,说明MySQL不得不边查边建临时表、再对结果集排序——这不是IO瓶颈,是纯CPU密集型任务。
- 典型场景:
ORDER BY t2.name但t2.name没索引,或GROUP BY用了右表字段 -
SELECT *会放大问题:拉回大字段(如TEXT、JSON)让排序和临时表构建更吃力 - 解决方向:把
ORDER BY字段限制在驱动表上;索引末尾加上SELECT需要的字段,实现覆盖索引
多表JOIN时驱动表选错,小表没驱动大表
优化器有时会误判行数,拿100万行的表当驱动表,去循环匹配1000行的表——相当于做100万次查找,而不是1000次。这时rows值会异常高,且EXPLAIN里id最小的表反而是大表。
- 验证方式:
EXPLAIN FORMAT=TREE看执行树结构,或SHOW PROCESSLIST观察状态卡在Sending data - 临时解法:
STRAIGHT_JOIN强制小表驱动,例如SELECT STRAIGHT_JOIN ... FROM small_config c JOIN big_log l ON c.id = l.config_id - 风险提示:旧版本MySQL只认
STRAIGHT_JOIN关键字,8.0+支持/*+ STRAIGHT_JOIN */提示,但一旦选错驱动表,性能反而更差
别调join_buffer_size,95%的问题根子在索引缺失
看到CPU飙高就去改join_buffer_size,等于给骨折打创可贴。真正起作用的是被驱动表ON字段有没有索引。
- 先确认是否真用到join buffer:
EXPLAIN里Extra出现Using join buffer (Block Nested Loop)才说明在用BNL - MySQL 8.0.22+默认禁用BNL、改用Hash Join,此时
join_buffer_size完全不生效 - 应急操作仅限调试:
SET SESSION join_buffer_size = 2097152,但必须配合STRAIGHT_JOIN和EXPLAIN验证Rows_examined是否下降
最容易被忽略的一点:三张表JOIN时,只要任意两张之间缺失有效索引,中间结果集就可能爆炸——比如A剩2行、B剩3行、C有100万行,组合起来就是600万行,CPU全耗在构造和过滤这个结果集上。索引检查必须逐表、逐JOIN条件做,不能漏掉中间表。











