一、底层机制解析

UNION
与UNION ALL
作为SQL集合操作的核心运算符,其性能差异源于数据处理的本质区别:
- 排序去重机制
当使用UNION
时,数据库引擎会自动执行以下流程:
sql
(SELECT * FROM table1)
UNION
(SELECT * FROM table2)
-- 等价于
SELECT DISTINCT * FROM (
SELECT * FROM table1
UNION ALL
SELECT * FROM table2
) AS combined_data
系统需要创建临时表存储中间结果集,执行排序操作消除重复记录。以MySQL为例,通过EXPLAIN
可观察到Using temporary
和Using filesort
标记。
- 数据扫描成本
UNION ALL
采用流式处理机制,仅需顺序扫描各子查询结果集。而UNION
的排序去重操作会引发以下额外消耗:- 内存/磁盘临时空间占用
- 索引失效风险(排序破坏原有索引顺序)
- CPU计算资源消耗(比较重复值)
二、性能对比实测
通过TPC-H测试数据集进行基准测试(单位:毫秒):
数据量 | UNION ALL | UNION | 差异倍数 |
---|---|---|---|
10万行 | 82 | 620 | 7.56x |
50万行 | 395 | 3,850 | 9.75x |
100万行 | 810 | 9,200 | 11.36x |
测试环境:MySQL 8.0,InnoDB引擎,SSD存储
随着数据量增长,UNION
的时间复杂度呈非线性上升趋势,主要瓶颈出现在:
- 临时表写入的I/O延迟
- 排序阶段的比较操作(O(n log n)复杂度)
- 内存不足时触发的磁盘交换
三、典型应用场景抉择
优先使用UNION ALL的情况
- 日志数据合并
处理访问日志、设备状态记录等天然具有时序特征的数据时:
sql
SELECT * FROM access_log_2023
UNION ALL
SELECT * FROM access_log_2024
- 维度表跨库查询
整合多个分库的用户信息表时,即便存在理论上的重复可能,实际业务中ID具有唯一性:
sql
SELECT user_id, name FROM user_db1.users
UNION ALL
SELECT user_id, name FROM user_db2.users
- 中间结果集处理
在CTE(通用表表达式)中构建中间数据集供后续筛选:
sql
WITH combined AS (
SELECT product_id FROM inventory
UNION ALL
SELECT item_id FROM archived_items
)
SELECT * FROM combined WHERE ...
必须使用UNION的场景
- 数据清洗阶段
合并多个来源的客户数据时消除重复记录:
sql
SELECT customer_id FROM crm_system
UNION
SELECT client_id FROM legacy_db
- 统计维度归并
当多个查询结果可能存在语义重复时(如不同统计口径的数据汇总):
sql
SELECT 'monthly', SUM(amount) FROM orders
UNION
SELECT 'quarterly', SUM(amount) FROM orders GROUP BY QUARTER(order_date)
四、高阶优化策略(接续前文)
1. 索引加速技巧
覆盖索引设计
当UNION
必须使用时,为子查询字段创建覆盖索引可减少排序阶段的磁盘I/O。以电商订单合并场景为例:
sql
-- 创建复合索引避免回表
CREATE INDEX idx_order_union ON orders(region, order_date, amount)
-- 优化后的UNION查询
SELECT region, order_date, amount FROM orders_asia
UNION
SELECT region, order_date, amount FROM orders_europe
哈希去重替代
在PostgreSQL等支持哈希聚合的数据库中,通过调整配置参数启用哈希去重:
sql
SET enable_sort = off;
SELECT * FROM table1
UNION
SELECT * FROM table2 -- 此时使用HashAggregate而非Sort
2. 执行计划干预
强制索引使用
当优化器错误选择全表扫描时,使用FORCE INDEX
提示:
sql
(SELECT * FROM log_2023 FORCE INDEX(timestamp_idx))
UNION
(SELECT * FROM log_2024 FORCE INDEX(timestamp_idx))
临时表优化
通过调整tmp_table_size
参数控制内存临时表使用:
ini
# MySQL配置
tmp_table_size = 64M
max_heap_table_size = 64M
3. 分布式架构适配
分片数据预处理
在TiDB等分布式数据库中,先在分片节点去重再汇总:
sql
-- 各分片执行
SELECT DISTINCT * FROM local_table
-- 协调节点执行
SELECT * FROM shard1_result
UNION ALL
SELECT * FROM shard2_result
并行执行控制
Oracle开启并行查询加速大数据量合并:
sql
SELECT /*+ PARALLEL(8) */ col1 FROM tab1
UNION ALL
SELECT /*+ PARALLEL(8) */ col1 FROM tab2
五、特殊场景解决方案
1. 海量数据分页陷阱
直接对UNION
结果分页会触发全量计算,采用子查询分层处理:
sql
WITH temp AS (
SELECT id, name, 1 AS source FROM big_table1
UNION ALL
SELECT id, name, 2 AS source FROM big_table2
)
SELECT * FROM temp
ORDER BY id LIMIT 100 OFFSET 10000
2. JSON数据合并处理
合并JSON字段时需注意格式一致性:
sql
SELECT JSON_EXTRACT(payload, '$.event') AS event
FROM log_2023
UNION ALL
SELECT CAST(data->>'action' AS JSON) -- 显式类型转换
FROM log_2024
3. 跨异构数据库同步
通过中间件转换字段类型后合并:
sql
-- 从MongoDB和MySQL同步数据
SELECT
_id AS doc_id,
title,
CAST(created_at AS DATETIME)
FROM mongo_articles
UNION ALL
SELECT
id,
title,
publish_time
FROM mysql_articles
六、最佳实践清单
-
预判数据特征
- 源表主键重叠率低于5%时优先
UNION ALL
- 高重复概率场景(>30%)可接受
UNION
成本
- 源表主键重叠率低于5%时优先
-
执行计划验证
- 关注
Extra
列中的Using temporary
警告 - 对比实际执行时间与估算值的偏差率
- 关注
-
资源监控指标
bash# MySQL内存监控 SHOW STATUS LIKE 'Created_tmp%'; # PostgreSQL排序统计 SELECT * FROM pg_stat_database WHERE datname = 'your_db';
-
降级兜底方案
当
UNION
超时时可分阶段执行:sql-- 第一阶段:存入临时表 CREATE TEMPORARY TABLE tmp_data AS SELECT * FROM table1 UNION ALL ...; -- 第二阶段:去重处理 SELECT DISTINCT * FROM tmp_data;
深度思考 :在云原生架构下,传统UNION
操作面临计算下推与数据分片的新挑战。建议结合物化视图实现预聚合,或采用UNION ALL +窗口函数
进行二次去重,例如:
sql
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER(PARTITION BY key_col) AS rn
FROM (
SELECT * FROM shard1
UNION ALL
SELECT * FROM shard2
) merged
) ranked
WHERE rn = 1
此模式可将去重压力分散到查询层,避免集中式排序带来的系统性风险。
🌟 让技术经验流动起来
▌▍▎▏ 你的每个互动都在为技术社区蓄能 ▏▎▍▌
✅ 点赞 → 让优质经验被更多人看见
📥 收藏 → 构建你的专属知识库
🔄 转发 → 与技术伙伴共享避坑指南
点赞 ➕ 收藏 ➕ 转发,助力更多小伙伴一起成长!💪
💌 深度连接 :
点击 「头像」→「+关注」
每周解锁:
🔥 一线架构实录 | 💡 故障排查手册 | 🚀 效能提升秘籍