union是行堆叠、join是列拼接,二者逻辑维度不同:union要求列数相同且类型兼容,结果列名取自第一个select;join依赖on条件匹配行,不关心列数。

UNION 是行堆叠,JOIN 是列拼接
这不是“选哪个更合适”的问题,而是根本不在一个维度上——UNION 操作的是两个 查询结果集,把它们上下摞起来;JOIN 操作的是两个 表的行,按条件横向拉宽。常见错误是写 SELECT a.id, a.name FROM users UNION SELECT b.order_id, b.amount FROM orders,直接报错:列数不一致、类型不兼容(INT vs DECIMAL),而这不是语法写错了,是逻辑错了。
列数和类型要求完全不同
UNION 强制要求每个 SELECT 的列数相同,且对应列类型要兼容(比如 TINYINT 和 INT 可隐式转换,但 VARCHAR(50) 和 DATE 不行);JOIN 完全不关心列数,只依赖 ON 子句里的字段是否可比(如 ON users.id = orders.user_id),而且该字段最好有索引。
- 列名以第一个
SELECT为准:SELECT name FROM t1 UNION SELECT full_name FROM t2,结果列名是name,不是full_name - 列顺序错位不会报错,但会静默错配:
SELECT id, nameUNIONSELECT name, id→ 第一列全是id值混进name字段 -
NULL在UNION中参与去重时被视为相等(NULL = NULL成立),但在JOIN中通常无法匹配(除非显式用IS NULL)
什么时候必须用 UNION ALL 而不是 JOIN
当你需要把结构相同、来源独立的数据合并在一个结果里,且不依赖行间关联关系。典型场景包括:
- 分年表查询:
SELECT user_id, login_date FROM user_active_2022 UNION ALL SELECT user_id, login_date FROM user_active_2023 - 多租户数据归并(各租户表结构一致)
- 跨时间分区拉取日志或事件记录
别用 UNION 去补字段——想把用户姓名和订单金额拼成一行?那是 JOIN 的事。用 UNION 干这个,结果要么报错,要么字段错位,查出来全是乱码数据。
性能和去重陷阱
UNION 默认去重,会触发临时排序和去重操作,大数据量时明显变慢;UNION ALL 直接堆叠,快得多——除非你真需要去重,否则优先用 UNION ALL。而 JOIN 的性能瓶颈通常在关联字段没索引、连接条件写错(比如漏 ON 导致笛卡尔积)、或嵌套过深。
最容易被忽略的是:UNION 的“兼容”不等于“语义一致”。两张表都有 id 和 name,不代表它们指代同一类实体——users.id 是用户主键,products.id 是商品主键,强行 UNION 出来的结果,业务上毫无意义。











