left join性能不如inner join,核心在于其语义强制左表全量扫描且驱动表顺序被锁死,优化器无法跳过左表或交换连接顺序,导致右表索引难生效、i/o开销倍增。

LEFT JOIN 不是天生就慢,而是它的语义让数据库“不敢优化”——必须扫完左表每一行,再逐条去右表找匹配,哪怕 95% 的行都找不到,也得硬扫。
LEFT JOIN 的驱动表被锁死,优化器没法换小表当主力
INNER JOIN 中,优化器可以自由选小表做驱动表(比如 customers 表只有 1 万行,orders 有 1000 万行,它就可能先扫 customers);LEFT JOIN 则不行——FROM users LEFT JOIN orders 意味着 users 必须是驱动表,哪怕它只有 100 行,orders 有 50 万行且带索引,优化器也不敢把 orders 提前。结果就是:左表每行都触发一次右表查找,I/O 和 CPU 开销直接乘上左表行数。
- 验证方法:用
EXPLAIN看table列顺序,以及rows是否接近左表总行数 - 典型症状:
type是ALL或index,rows高达几十万,但实际结果只有几百行 - MySQL 5.7/8.0 默认不重排 LEFT JOIN 顺序,PostgreSQL 的
join_collapse_limit超过阈值也会退化为固定顺序
WHERE 条件写错位置,LEFT JOIN 白忙一场
写 LEFT JOIN orders o ON u.id = o.user_id WHERE o.status = 'paid',表面想筛已支付订单,实际效果是:先连出所有用户 + NULL 订单,再把 NULL 行全干掉——语义退化成 INNER JOIN,但执行路径仍是 LEFT JOIN 的全套开销。
- 正确做法是把右表过滤塞进
ON:LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid' - 或者用子查询提前收缩右表:
LEFT JOIN (SELECT * FROM orders WHERE status = 'paid') o ON u.id = o.user_id - 只要
WHERE出现右表字段的非空判断(如o.id IS NOT NULL),基本就等于告诉优化器:“我其实只要交集”,但它还按 LEFT JOIN 流程走
右表连接字段没索引 or ON 里写了函数,性能直接雪崩
LEFT JOIN 对右表索引失效更敏感。因为驱动表行数多,每次失效都放大 N 倍:左表 10 万行 × 右表全表扫描 = 10 万次全表扫描。
- 常见索引失效写法:
ON a.id = CAST(b.ref_id AS CHAR)、ON a.code = UPPER(b.code)、ON a.id = b.ref_id + 0 - 复合索引没对齐最左前缀:
INDEX(user_id, status),但ON u.id = o.user_id AND o.status = 'paid'中status不在最左,Seek 可能变 Scan - 修复建议:确保连接字段两侧类型一致、无函数包装;必要时建函数索引(MySQL 8.0+ 支持
CREATE INDEX idx_ref_upper ON b ((UPPER(ref_id))))
统计信息陈旧 or JOIN 链太长,优化器直接“摆烂”
三张以上 LEFT JOIN 连写(FROM a LEFT JOIN b ON ... LEFT JOIN c ON ... LEFT JOIN d ON ...),MySQL 默认 optimizer_search_depth = 6,搜索空间爆炸,优化器干脆放弃找最优路径,按你写的顺序硬执行。
- 表现:
EXPLAIN FORMAT=TREE显示嵌套极深,最内层是table_b全表扫描;某步耗时占整体 90% 以上 - 临时缓解:
/*+ SET_VAR(optimizer_search_depth = 8) */(MySQL 8.0+),但不如拆成 CTE 或子查询来得可控 - 别忽略
ANALYZE TABLE(MySQL)或VACUUM ANALYZE(PostgreSQL)——统计不准时,优化器连该不该用索引都猜错
真正卡住 LEFT JOIN 的,往往不是语法本身,而是 ON 条件是否干净、右表是否有合适索引、WHERE 是否误伤语义、以及你有没有意识到:当业务只要“有匹配的数据”时,INNER JOIN 不是妥协,而是更精准的选择。










