MySQL 5.7多表JOIN性能瓶颈主因是执行计划错误,需紧盯EXPLAIN的type、rows、select_type三列:type=ALL即全表扫描,rows远超实际匹配行数说明驱动表过大或条件未下推,select_type=DERIVED异常提示derived_merge干扰;索引失效多因字符集/类型不一致或ON中用函数;强制JOIN顺序须用STRAIGHT_JOIN并人工评估驱动表;子查询变慢应关derived_merge或重构为JOIN。

MySQL 5.7 的多表关联查询性能瓶颈,90% 不是 SQL 写得差,而是执行计划没走对——尤其当 EXPLAIN 显示 type=ALL 或 rows 高得离谱时,基本可以断定驱动表选错、索引没用上,或子查询被错误合并。
怎么看执行计划里哪张表在拖后腿
重点盯紧 EXPLAIN 输出的三列:type、rows、select_type。只要其中任意一行出现 type=ALL,就说明这张表正在全表扫描;rows 值远超该表实际匹配行数(比如预估 50 万,实际只返回 100 行),大概率是驱动表过大或条件没下推;若 select_type=DERIVED,说明子查询被物化,但若本该是 DERIVED 却显示 SIMPLE,反而要警惕 derived_merge=on 把 GROUP BY 合并掉了,导致重复扫描。
- 用
EXPLAIN FORMAT=TREE(MySQL 5.7 不支持,改用EXPLAIN+SHOW WARNINGS看扩展信息)确认 JOIN 顺序是否符合预期 - 对每个
ON字段单独跑EXPLAIN SELECT * FROM t2 WHERE t2.join_col = ?,验证索引是否真能命中 - 注意
key_len是否符合预期:比如联合索引(a,b,c),若key_len只显示 a 的字节数,说明 b 和 c 没参与查找
为什么明明建了索引,JOIN 还是全表扫描
索引失效在多表 JOIN 中比单表更隐蔽。最常见原因是字符集不一致或隐式类型转换——哪怕两个字段都叫 user_id,一边是 BIGINT,一边是 VARCHAR,JOIN 就无法用索引;同理,utf8mb4 和 utf8 混用也会让索引失效。另一个高频坑是 ON 条件里用了函数,比如 ON DATE(t1.create_time) = t2.date,哪怕 t1.create_time 有索引,也完全作废。
- 检查两张表对应 JOIN 字段的
COLUMN_TYPE和COLLATION_NAME是否完全一致(查INFORMATION_SCHEMA.COLUMNS) - 避免在
ON或WHERE中对索引字段使用任何函数、表达式或运算符(如+ 0、LOWER()) - LEFT JOIN 的右表如果带 WHERE 条件(如
WHERE t2.status = 'ok'),等价于 INNER JOIN,但优化器可能仍按 LEFT 逻辑处理,导致索引无法下推——此时显式改写为 INNER JOIN 更安全
怎么强制控制 JOIN 顺序不被优化器乱改
别信书写顺序。MySQL 5.7 的优化器会重排表顺序,LEFT JOIN t2 ON ... JOIN t3 ON ... 完全不能保证 t2 先于 t3 执行。真正可控的方式只有 STRAIGHT_JOIN,但它只对主查询生效,对子查询、派生表无效。
- 在
SELECT关键字后加/*+ STRAIGHT_JOIN */(MySQL 5.7 不支持 optimizer hint,得用STRAIGHT_JOIN关键字本身):SELECT STRAIGHT_JOIN ... FROM t1 JOIN t2 ON ... JOIN t3 ON ... - 用之前必须人工评估各表过滤后的结果集大小:驱动表应是 WHERE 条件筛选后行数最少的那个,而不是物理体积最小的表
- UPDATE JOIN 语句不能直接加
STRAIGHT_JOIN,得先用等价SELECT跑EXPLAIN验证,再套回 UPDATE
子查询被合并后变慢,怎么让它老老实实物化
MySQL 5.7 默认 optimizer_switch='derived_merge=on',遇到含 GROUP BY、DISTINCT 或聚合的子查询,常因合并失败引发重复扫描,甚至在 UPDATE ... WHERE id IN (SELECT ...) 场景直接报 ER_UPDATE_TABLE_USED 错误。
- 临时方案:会话级关闭合并,
SET SESSION optimizer_switch='derived_merge=off';,但要注意max_heap_table_size太小会导致物化表溢出磁盘 - 长期方案:建视图时硬编码
ALGORITHM=TEMPTABLE,如CREATE VIEW v_summary AS SELECT user_id, COUNT(*) c FROM logs GROUP BY user_id ALGORITHM=TEMPTABLE;,此设置优先级高于derived_merge - 终极方案:把子查询逻辑手动拆进主查询,用
JOIN替代IN,并显式控制过滤时机(比如先 WHERE 再 JOIN),避免优化器瞎猜
真正难的不是让子查询物化,而是物化之后的临时表既没索引也没统计信息,你连 HINT 都加不上——这时候就得接受一个事实:有些查询,必须靠重构逻辑来换性能,而不是靠调参数。


















