MySQL 5.7 和 SQLite 不支持 FULL JOIN 是因引擎层缺失,非配置问题;需用 LEFT JOIN + RIGHT JOIN 等价组合(如 LEFT JOIN 交集与左独有 + NOT EXISTS 获取右独有)并 UNION ALL 模拟,注意 NULL 安全和性能开销。

为什么 FULL JOIN 在 MySQL 5.7 或 SQLite 中直接报错?
因为这些系统压根不实现 FULL JOIN 语法——MySQL 直到 8.0.28 才实验性支持(且需开启 optimizer_switch),SQLite 至今完全不识别。你写的 FULL JOIN 会被解析器当成语法错误,典型报错是 near "FULL": syntax error。
别试图“开启某个开关”让它生效,这不是配置问题,是引擎层缺失。必须用标准 SQL 的等价组合来模拟语义。
用 LEFT JOIN + RIGHT JOIN + UNION ALL 拼出完整结果集
FULL JOIN 的本质是:保留左表所有行、右表所有行,匹配不上就补 NULL。这个逻辑可拆解为三部分:
– 左表有、右表也有的(交集)
– 左表有、右表没有的(左独有)
– 右表有、左表没有的(右独有)
实操时最稳的方式是用 LEFT JOIN 覆盖前两项,再用 RIGHT JOIN(或等价的 LEFT JOIN 反向写法)取第三项,最后 UNION ALL 合并:
SELECT l.id, l.name, r.score FROM users l LEFT JOIN scores r ON l.id = r.user_id UNION ALL SELECT NULL AS id, NULL AS name, r.score FROM scores r WHERE r.user_id NOT IN (SELECT id FROM users);
注意点:
- 第二段查询里
r.user_id NOT IN (...)要小心NULL值导致整个条件失效,更安全的写法是NOT EXISTS (SELECT 1 FROM users u WHERE u.id = r.user_id) - 字段顺序和类型必须严格一致,否则
UNION ALL会失败;显式写NULL AS xxx比依赖隐式转换更可控 - 如果原
FULL JOIN有WHERE条件,得拆到两个子查询里分别加,不能只加在外部
当连接键含 NULL 时,NOT IN 会静默丢数据
这是最容易踩的坑:如果 users.id 或 scores.user_id 允许为 NULL,那么 r.user_id NOT IN (SELECT id FROM users) 整个表达式会返回空结果集——因为 NULL NOT IN (..., NULL) 永远为 UNKNOWN,被当作 FALSE 处理。
必须改用 NOT EXISTS 或手动处理 NULL:
SELECT l.id, l.name, r.score FROM users l LEFT JOIN scores r ON l.id = r.user_id UNION ALL SELECT NULL, NULL, r.score FROM scores r WHERE NOT EXISTS (SELECT 1 FROM users u WHERE u.id = r.user_id);
或者更彻底地兼容 NULL 连接键:
WHERE (r.user_id NOT IN (SELECT id FROM users WHERE id IS NOT NULL)
AND r.user_id IS NOT NULL)
OR (r.user_id IS NULL AND NOT EXISTS (SELECT 1 FROM users u WHERE u.id IS NULL));
性能比原生 FULL JOIN 差很多,但这是旧系统的现实
上述方案要扫两次右表(一次 LEFT JOIN,一次 NOT EXISTS 子查询),还多一次 UNION ALL 合并开销。如果右表几百万行,执行时间可能翻倍。
优化方向有限,但可做:
- 确保
users.id和scores.user_id都有索引(单列或联合索引) - 避免在
NOT EXISTS子查询里 SELECT *,只查SELECT 1 - 如果业务允许,先用
CREATE TEMPORARY TABLE缓存左表主键,再 JOIN,有时比反复子查询快
真正麻烦的是嵌套层级深的查询——每层 FULL JOIN 都得展开成两路,SQL 体积和维护成本指数级上升。这种时候,不如在应用层分步查再合并。

















