90%的慢查询源于join字段未建索引;需检查执行计划、确保数据类型一致、选对join类型、合理选择驱动表,并善用覆盖索引、物化cte等轻量手段。

JOIN字段没加索引,90%的慢查询根源在这里
绝大多数 JOIN 变慢,不是因为 SQL 写得复杂,而是 ON 条件里的字段没建索引。数据库执行 JOIN 时,如果驱动表(左表)能走索引快速定位,被驱动表(右表)又在关联字段上有索引,就能避免全表扫描。
- 检查执行计划:用
EXPLAIN看type是否为ref或eq_ref;若出现ALL,基本说明缺失索引 - 复合索引要注意顺序:比如
JOIN a ON a.id = b.a_id,应在b(a_id)上建单列索引;若还有WHERE b.status = 'active',可考虑b(a_id, status) - 注意数据类型一致:
INT字段 JOINVARCHAR字段会隐式转换,导致索引失效 —— 错误示例:users.id (INT) JOIN orders.user_id (VARCHAR)
LEFT JOIN 还是 INNER JOIN?语义不对就白优化
选错 JOIN 类型不仅逻辑出错,还会让优化器放弃使用某些索引或合并策略。比如本该用 INNER JOIN 的场景用了 LEFT JOIN,数据库必须保留左表所有行,无法提前过滤右表,执行计划往往更保守。
-
LEFT JOIN在右表无匹配时返回 NULL 行 —— 如果业务上明确只要“有订单的用户”,就该用INNER JOIN users u JOIN orders o ON u.id = o.user_id - 当
WHERE条件写在右表字段上(如WHERE o.created_at > '2024-01-01'),LEFT JOIN实际退化为INNER JOIN,但优化器未必识别,建议显式改写 - MySQL 8.0+ 支持
STRAIGHT_JOIN强制连接顺序,但仅在确认驱动表更小时才用,否则反而更慢
大表 JOIN 小表,谁当驱动表很关键
JOIN 性能高度依赖驱动表的选择。优化器通常选行数少的表作驱动表,但如果统计信息不准(比如没 ANALYZE TABLE),它可能选错 —— 导致小表被循环扫描多次。
- 用
EXPLAIN FORMAT=TREE(MySQL 8.0+)或EXPLAIN中的rows列对比两张表预估扫描行数 - 对小表(比如
status_codes只有 10 行),确保它作为驱动表;若优化器反过来了,可加STRAIGHT_JOIN或重写为子查询:SELECT * FROM (SELECT id FROM small_table WHERE ...) s JOIN big_table b ON s.id = b.small_id - 避免在 JOIN 条件里用函数:如
ON DATE(o.created_at) = DATE(u.registered_at)会让索引完全失效
临时表、物化 CTE 和覆盖索引这些“轻量级”提速手段
不改表结构、不加索引的情况下,仍有几个低侵入方式可缓解 JOIN 压力,但每种都有适用边界。
- 覆盖索引:让
SELECT所需字段全部落在联合索引中,避免回表。例如SELECT u.name, u.email FROM users u JOIN orders o ON u.id = o.user_id WHERE o.status = 'paid',可在orders(status, user_id, id)上建索引(id是为了回查用户,但若只需user_id就够了,就不需要) - 物化 CTE(MySQL 8.0+ / PostgreSQL):把中间结果缓存一次,避免重复计算,适用于多层 JOIN 中某张表被多次引用
- 临时表分步处理:对超大表先
CREATE TEMPORARY TABLE tmp_orders AS SELECT user_id FROM orders WHERE ...,再 JOIN,比直接 JOIN 带 WHERE 的大表更可控
真正卡住性能的,往往是“以为加了索引就万事大吉”,却忽略了数据分布倾斜、统计信息陈旧、字符集隐式转换这些细节。跑一遍 ANALYZE TABLE,再看 EXPLAIN,比盲目调优更有效。










