根本原因是驱动表返回行数过多、优化器误判或连接字段隐式类型转换;需统一字段类型、更新统计信息、避免NULL影响,并用EXPLAIN FORMAT=TREE验证真实执行路径。

为什么EXPLAIN显示Nested Loop却没走索引
根本原因往往是驱动表(左表)返回了过多行,优化器误判了索引有效性,或者连接字段存在隐式类型转换。比如user_id在左表是INT,右表却是VARCHAR,MySQL会放弃右表索引,强制走全表扫描。
- 先用
EXPLAIN FORMAT=TREE(MySQL 8.0+)看真实执行路径,比传统EXPLAIN更准 - 检查
EXTRA列是否含Using join buffer (Block Nested Loop)——这是性能杀手,说明没走索引JOIN - 对连接字段统一类型、加联合索引(如
(status, user_id)),避免单列索引被忽略 - 临时禁用BNL:在会话中执行
SET SESSION optimizer_switch='block_nested_loop=off';验证是否真由它拖慢
LEFT JOIN变INNER JOIN后查询快10倍的真相
不是语法本身变快,而是优化器因语义放宽而选了更优驱动表。LEFT JOIN要求保留左表所有行,常迫使左表当驱动表;一旦改成INNER JOIN,优化器可自由选择小表驱动大表。
- 确认业务是否真需要LEFT语义——很多“习惯性LEFT”其实只要INNER结果
- 用
STRAIGHT_JOIN强制指定驱动表顺序(如SELECT STRAIGHT_JOIN ... FROM small_table JOIN big_table ON ...) - 给驱动表加
WHERE条件缩小结果集,比单纯调换JOIN顺序更可靠 - 注意
NULL值影响:如果连接字段允许NULL,即使改INNER也可能因NULL匹配逻辑退化成全扫
JOIN顺序手动调整但执行计划不变怎么办
MySQL 5.7+默认启用join_cache_level和基于成本的重排序,你写的顺序只是hint,不一定生效。真正起作用的是统计信息准确度和索引覆盖程度。
- 更新统计信息:
ANALYZE TABLE order_detail;(别只analyze主表) - 删掉无用索引,尤其前缀重复的索引(如已有
(a,b),再建(a)会干扰优化器判断) - 用
FORCE INDEX锁定关键连接字段的索引:FROM orders FORCE INDEX (idx_user_status) JOIN ... - 检查
innodb_stats_persistent是否开启,否则重启后统计信息丢失,优化器又乱猜
大表JOIN时磁盘临时表爆满的应急方案
Created_tmp_disk_tables飙升说明JOIN中间结果写到了磁盘,通常是sort_buffer_size或join_buffer_size太小,或没走索引导致大量数据参与JOIN。
- 临时加大内存:会话级设
SET SESSION join_buffer_size = 268435456;(256MB),但别超物理内存30% - 用
SELECT ... INTO OUTFILE把小表预处理成CSV,再用LOAD DATA INFILE导入临时表并建好索引 - 拆分JOIN:先用
INSERT INTO tmp_user_ids SELECT DISTINCT user_id FROM orders WHERE ...,再用IN (SELECT ...)代替JOIN - 注意
tmp_table_size和max_heap_table_size必须设为相同值,否则内存表仍可能落地磁盘
最常被忽略的是统计信息过期和隐式类型转换——这两点不解决,调啥JOIN顺序、加多少内存都白搭。










