full outer join几乎必然触发全表扫描,因其必须保留左右表所有行并补null,无法剪枝,优化器退化为双路全扫描+合并去重,explain中type=all且rows接近总行数。

为什么FULL OUTER JOIN几乎必然触发全表扫描
因为数据库无法剪枝:FULL OUTER JOIN必须保留左表和右表的所有行,对不匹配的补NULL。优化器没法像INNER JOIN那样跳过不匹配块,也没法像LEFT JOIN那样只驱动一侧。多数引擎会退化为“双路全扫描 + 合并去重”,EXPLAIN里常看到type=ALL出现在两边,rows接近各自总行数。
分区交换(PARTITION SWITCH)对FULL OUTER JOIN完全无效
分区交换是DDL操作,只用于快速替换整个分区数据,不参与查询执行计划生成。它既不改变连接算法,也不减少扫描行数。试图用SWITCH PARTITION优化FULL OUTER JOIN属于典型误用——哪怕左右表都按date_key做了分区,只要连接条件不是严格等于分区键且无其他过滤,照样全扫。
真正能落地的替代写法:UNION + 双LEFT JOIN
90% 的业务场景其实只需要“所有主键存在记录的并集”,而非字面意义的完全外连接。可安全改写为:
SELECT id, COALESCE(t1.val, t2.val) AS val FROM ( SELECT id FROM t1 UNION SELECT id FROM t2 ) keys LEFT JOIN t1 ON keys.id = t1.id LEFT JOIN t2 ON keys.id = t2.id;
这个结构的关键优势:
-
UNION自动去重,生成最小驱动集; - 两个
LEFT JOIN都能走id上的索引(确保t1.id和t2.id都有单列索引); - 避免了
FULL OUTER JOIN特有的哈希全集或排序合并开销; - 如果
id字段允许NULL,记得在UNION子句中加WHERE id IS NOT NULL,否则索引可能被忽略。
如果非用FULL OUTER JOIN不可,至少做三件事
MySQL 8.0+ 和 PostgreSQL 13+ 支持并行扫描,但效果高度依赖前提条件:
- 确认
EXPLAIN输出里有Worker(MySQL)或Gather(PostgreSQL)节点,没有就说明并行没启用; - 左右表连接列必须都有索引,且统计信息准确(定期运行
ANALYZE TABLE t1, t2); - 连接字段类型必须严格一致,比如
t1.user_id INT和t2.user_id BIGINT会导致隐式转换,索引失效; - 避免在
ON里写函数,例如ON t1.code = UPPER(t2.code)会让t2.code索引彻底作废。
真正难处理的不是语法怎么写,而是业务是否真的需要“所有行都保留”。很多所谓FULL OUTER JOIN,其实是前端分页、权限过滤或数据校验逻辑没下沉到SQL层导致的伪需求——先确认这点,比调优执行计划更省力。










