mysql在8.0.29前不支持full outer join语法,需用left join+right join+union模拟;8.0.29+虽支持但默认禁用,需启用optimizer_switch参数。

MySQL 为什么没有 FULL OUTER JOIN
因为 MySQL 在 8.0.29 之前压根不支持 FULL OUTER JOIN 语法,直接执行会报错:ERROR 1064 (42000): You have an error in your SQL syntax。这不是你写错了,是引擎根本不认这个关键字。PostgreSQL、SQL Server、Oracle 都支持,但 MySQL 偏要走自己的路——得靠组合拳模拟。
用 LEFT JOIN + RIGHT JOIN + UNION 拼出全连接效果
核心思路:左连接覆盖“左表有、右表无”的行,右连接覆盖“右表有、左表无”的行,再用 UNION 合并(自动去重),最后补上两表都有的交集部分——其实 LEFT JOIN 和 RIGHT JOIN 已经各自包含了交集,所以直接并起来就对了,不用额外加 INNER JOIN。
实操时注意三点:
-
SELECT列顺序和数量必须完全一致,否则UNION会失败;缺字段的用NULL补位 - 用
UNION ALL虽快,但可能重复输出交集行(因左右连接各算一次),所以坚持用UNION - 连接条件字段类型要一致,否则
ON a.id = b.user_id可能因隐式转换导致索引失效
示例(查用户表和订单表的全连接):
SELECT u.id, u.name, o.order_id, o.amount FROM users u LEFT JOIN orders o ON u.id = o.user_id UNION SELECT u.id, u.name, o.order_id, o.amount FROM users u RIGHT JOIN orders o ON u.id = o.user_id;
NULL 值处理和字段对齐是最大坑点
左右连接中缺失侧的字段值全是 NULL,但人容易忽略:如果 orders 表里有 created_at 字段而 users 没有对应列,那在左连接分支里就得显式写 NULL AS created_at,否则右边分支的 SELECT 就会列数不匹配。
常见翻车场景:
- 一边
SELECT u.id, u.name, o.order_id,另一边漏了o.order_id或写成o.id—— 直接报Column count doesn't match - 用了
UNION ALL却没意识到交集行被重复计算两次,结果多出一倍数据 -
ON条件里混用字符串和数字(比如u.code = o.user_code,但一个是VARCHAR一个是INT),MySQL 强制转类型,索引失效且结果不准
性能比原生 FULL OUTER JOIN 差不少,别在大表上硬刚
这个方案本质是跑两次连接 + 一次去重合并,IO 和内存开销翻倍。10 万行以下还行,超百万行建议换思路:
- 先用
CREATE TEMPORARY TABLE分别存左右连接结果,加好索引再UNION - 如果业务只要“某侧缺失”的数据,优先用
NOT EXISTS或LEFT JOIN ... WHERE right_table.id IS NULL单独查,比全连接快得多 - MySQL 8.0.29+ 确实加了
FULL OUTER JOIN支持,但默认仍禁用,需启动时加参数--optimizer_switch='full_join=on',生产环境改这个要格外小心
真正上线前,务必用 EXPLAIN FORMAT=TREE 看执行计划,确认没走全表扫描——那个 UNION 很容易把优化器搞懵。











