union自动去重但性能差,需排序和临时表;若数据天然无重复应改用union all提升2–5倍性能;列数、类型须一致,隐式转换有风险;order by/limit仅限末尾;不可update/delete;去重基于整行而非单列。

UNION 会自动去重,但代价是排序和临时表
UNION 默认行为是去重合并,本质是执行 SELECT DISTINCT + 隐式 ORDER BY(MySQL 8.0.19+ 后不再强制排序,但仍需去重逻辑)。这意味着它必须把所有结果集先拉到内存或磁盘临时表里,再做唯一性判断——数据量一大就明显变慢。
常见错误现象:UNION 查询比单个子查询慢几倍,甚至触发 Using temporary; Using filesort;或者在 EXPLAIN 里看到 Extra 列出现 Using temporary。
- 如果确定两个结果集天然无重复(比如查不同日期的订单、不同状态的用户),直接用
UNION ALL,性能通常能提升 2–5 倍 -
UNION要求各子查询列数相同、对应列类型兼容;类型不一致时 MySQL 会隐式转换,可能出意外(比如VARCHAR和TEXT比较时截断) - 列名以第一个子查询为准,后续子查询的列别名无效;想统一字段名,只能在第一个子查询里起别名
列类型不匹配时,UNION 的隐式转换很危险
MySQL 对 UNION 各子查询对应列取“最小公分母”类型:比如一个子查询是 TINYINT,另一个是 INT,最终结果列类型会升为 INT;但如果一个是 VARCHAR(10),另一个是 VARCHAR(255),就会按长的那个定宽——看起来没事,但若后续接 GROUP BY 或索引字段,可能因长度超出导致无法使用索引。
使用场景:合并日志表(log_202401 和 log_202402)时,某列在旧表是 CHAR(8),新表改成了 VARCHAR(16),UNION 后该列实际变成 VARCHAR(16),但隐式转换可能让优化器误判 selectivity。
- 用
SHOW CREATE TABLE确认各表对应列的实际类型和长度 - 必要时手动
CAST统一,比如CAST(user_id AS UNSIGNED),避免依赖隐式规则 - 特别警惕
NULL和空字符串混用:一个子查询返回NULL,另一个返回'',UNION会视为不同值,但业务上可能等价
ORDER BY 和 LIMIT 只能写在最后,不能每个子查询单独加
UNION 是集合操作,语法上只允许整个语句末尾有一个 ORDER BY 和一个 LIMIT。如果在子查询里写 ORDER BY,MySQL 会报错 This type of clause is not allowed in a UNION(除非配合 LIMIT 写成子查询嵌套)。
Shopxp购物系统历经多年的考验,并在推出shopxp免费购物系统下载之后,收到用户反馈的各种安全、漏洞、BUG、使用问题进行多次修补,已经从成熟迈向经典,再好的系统也会有问题,在完善的系统也从在安全漏洞,该系统完全开源可编辑,当您下载这套商城系统之后,可以结合自身的技术情况,进行开发完善,当然您如果有更好的建议可从官方网站提交给我们。Shopxp网上购物系统完整可用,无任何收费项目。该系统经过
容易踩的坑:想“先取每个表最新 10 条再合并”,直接写 (SELECT * FROM t1 ORDER BY ts DESC LIMIT 10) UNION (SELECT * FROM t2 ORDER BY ts DESC LIMIT 10) 会失败。
- 正确做法是把子查询包进括号,当成派生表:
(SELECT * FROM t1 ORDER BY ts DESC LIMIT 10) AS a UNION (SELECT * FROM t2 ORDER BY ts DESC LIMIT 10) AS b - 但注意:这样写后,外层
ORDER BY才能生效;否则合并结果顺序不可控 - 如果真要控制每个子集的排序逻辑,得用
ROW_NUMBER()窗口函数(MySQL 8.0+),而不是依赖UNION内部顺序
UNION 结果集无法直接 UPDATE 或 DELETE
MySQL 不允许对 UNION 查询结果执行 UPDATE 或 DELETE,会报错 You can't specify target table for update in FROM clause(即使没显式写 FROM)。这不是 bug,是语法限制:UNION 返回的是虚拟结果集,没有物理表对应。
使用场景:想“找出 A 表和 B 表中 status=0 的所有 id,然后批量更新为 1”——不能写 UPDATE (SELECT id FROM a WHERE status=0 UNION SELECT id FROM b WHERE status=0) SET status = 1。
- 拆成两步:先用
UNION查出 id 列表,再用IN或临时表驱动更新 - 更稳妥的是用
INSERT ... ON DUPLICATE KEY UPDATE或REPLACE INTO配合临时表中转 - 如果只是去重后导出数据,
UNION完全够用;但凡涉及写操作,就得绕开它
最常被忽略的一点:UNION 的去重逻辑基于整行比较,不是单列。哪怕你只关心 id 去重,它也会把 id 相同但其他列不同的行全留下——因为行不完全重复。真要按某列去重,得用 GROUP BY 或窗口函数,而不是依赖 UNION。









