JOIN慢的核心是中间结果集失控,应优先用高选择性表驱动、小表驱动大表,LEFT JOIN右表过滤条件须放ON子句,避免误转INNER JOIN,并先排查单表全表扫描瓶颈。

三表以上JOIN慢,核心问题往往不在SQL写法本身,而在于中间结果集失控——先连两个大表,再连第三个表时,临时行可能已膨胀到百万级,哪怕最终WHERE只留10条也没用。
为什么JOIN顺序直接影响执行效率
数据库优化器会重排JOIN顺序,但你写的顺序决定了中间结果集的“初始膨胀点”。比如orders JOIN users JOIN products,若orders和users都无过滤条件,光这两张表笛卡尔积就可能超千万行,再连products只会雪上加霜。
- 用
EXPLAIN看rows列:如果某次JOIN前的rows突然跳到几十万,说明它就是膨胀源头 - 高选择性表优先:带强
WHERE条件(如status = 'paid')的表应尽量放在JOIN链开头,让中间结果从一开始就被压小 - 小表驱动大表仍是有效经验:比如
class表只有20行,student有10万行,先class JOIN student比反过来更稳 - MySQL中可用
STRAIGHT_JOIN强制顺序,但前提是ANALYZE TABLE已更新统计信息,否则可能适得其反
LEFT JOIN后对右表字段加WHERE等于变INNER JOIN
这是线上事故高频点。写成LEFT JOIN b ON a.id = b.a_id WHERE b.status = 'paid',实际效果等同于INNER JOIN——因为WHERE会把右表为NULL的记录全过滤掉。
- 正确做法是把右表过滤条件挪进
ON子句:LEFT JOIN b ON a.id = b.a_id AND b.status = 'paid' - 检查
EXPLAIN的Extra列:若出现Using where且涉及右表字段,就是危险信号 - 如果业务真需要“左表全量 + 右表按状态匹配”,但又想保留
NULL行,必须确认右表该字段允许NULL,否则LEFT JOIN失去意义
物化视图不是万能解药,MySQL原生不支持
PostgreSQL、Oracle或SQL Server可建物化视图缓存多表JOIN结果,但MySQL直到8.0仍无原生物化视图支持。强行模拟(如用定时INSERT INTO ... SELECT刷新临时表)反而容易引发一致性问题。
- 真正可行的替代方案是:对稳定维度表(如
course、class)与事实表(如score)的常用组合,建宽表并每日增量更新 - 宽表字段命名需带来源标识,比如
course_subject、class_name,避免后续JOIN时字段歧义 - 如果只是为提速报表查询,优先考虑在应用层做二级缓存(如Redis存聚合结果),比数据库层硬扛JOIN更可控
- 注意宽表索引策略:复合索引要覆盖常用查询条件,比如报表常查
class_name = '高三(1)班' AND course_subject = 'Python编程',那就建(class_name, course_subject)索引
最易被忽略的点是:你以为在优化JOIN,其实瓶颈可能在单表扫描——比如orders表没对order_date建索引,那无论怎么调JOIN顺序,第一步扫全表就已注定慢。先看EXPLAIN里哪个表的type是ALL,再动手。

















