INNER JOIN通常比LEFT JOIN快,因其支持提前剪枝,无需处理NULL填充、右表全扫描及保守执行计划等LEFT JOIN隐性开销。

INNER JOIN 通常比 LEFT JOIN 快,但原因不在“类型本身”
快不是因为 INNER JOIN 语法更轻量,而是它天然支持“提前剪枝”:数据库在连接过程中一旦发现右表无匹配,整行直接丢弃,不参与后续填充、NULL 处理或结果集构建。而 LEFT JOIN 必须先确保左表每行都进入结果,再为每行尝试查找右表匹配项——哪怕 90% 的左表行在右表都找不到对应,也得走完这个流程。
真正拉大性能差距的往往是这些隐性开销:
-
LEFT JOIN需要额外分配内存存储 NULL 填充字段,尤其当右表宽(列多)时,结果集体积明显膨胀 - 若右表连接字段没索引,
LEFT JOIN可能触发对右表的多次全扫描(每条左表记录都扫一遍),而INNER JOIN在优化器判断无匹配后可快速跳过 - 某些数据库(如 MySQL 5.7)对
LEFT JOIN的执行计划生成更保守,容易错过基于统计信息的最优路径
ON 条件里写函数会让 LEFT JOIN 性能雪崩
比如把 ON a.id = UPPER(b.ref_id) 这种写法用在 LEFT JOIN 中,会导致右表 b 完全无法使用 ref_id 上的索引——因为函数操作使索引失效。此时数据库只能对右表逐行计算 UPPER() 再比对,数据量稍大就卡住。
正确做法是确保连接字段两侧都保持原始形态:
- 如果
b.ref_id存的是小写,就统一存小写,别在 ON 里转大写 - 实在需要大小写不敏感匹配,改用带校对规则的列(如
COLLATE utf8mb4_0900_as_cs)或建函数索引(MySQL 8.0+ 支持CREATE INDEX idx_ref_upper ON b ((UPPER(ref_id)))) -
INNER JOIN同样受此影响,但因行数少,恶化程度常被掩盖
WHERE 条件放错位置,LEFT JOIN 就白写了
这是最隐蔽也最致命的性能陷阱:LEFT JOIN 后跟 WHERE b.status = 'paid',表面看是想筛已支付订单,实际效果是把所有 b.status 为 NULL 的行(即左表无匹配的行)全过滤掉——结果和 INNER JOIN 完全一致,还多花了 LEFT JOIN 的初始化成本。
该写在哪?记住口诀:“连什么”写 ON,“要不要这行”写 WHERE:
- 只关联已支付订单 → 放
ON:LEFT JOIN b ON a.id = b.a_id AND b.status = 'paid' - 查所有用户,但只显示已支付订单的金额 → 仍放
ON,否则WHERE一加,没订单的用户就消失了 - 真要全局过滤(比如排除测试用户),才用
WHERE a.is_test = 0,且必须作用于左表字段
LEFT JOIN 行数等于左表行数,但结果集未必更“重”
很多人以为 LEFT JOIN 因返回更多行,一定更慢。其实不然:如果右表匹配率高(比如 95% 行都能连上),且连接字段有高效索引,LEFT JOIN 和 INNER JOIN 的执行时间可能相差无几。反倒是 INNER JOIN 在高倾斜数据下(如左表 10 万行,仅 100 行能连上),因优化器误判选择嵌套循环而非哈希连接,反而更慢。
关键看三点:
- 左表行数是否远大于右表匹配行数(倾斜越严重,
LEFT JOIN开销越不可控) - 右表连接字段是否有索引(没有的话,
LEFT JOIN是灾难) - 是否用了
SELECT *(LEFT JOIN下取右表全部列会放大 NULL 填充成本,应显式列出需要的列)
执行计划里重点关注 rows 和 Extra 字段:如果看到 Using where; Using join buffer 或反复出现 Range checked for each record,基本可以确定 LEFT JOIN 正在裸奔。


















