1. 理解UNION与UNION ALL的本质区别
当我们需要合并多个SQL查询结果集时,UNION和UNION ALL是最常用的两个操作符。虽然它们都能实现结果集的合并,但在底层处理机制上存在关键差异。
UNION操作符会对合并后的结果集进行去重操作。这意味着如果两个查询结果中存在完全相同的行,UNION只会保留其中一行。这个去重过程实际上包含了排序和比较的操作,因此会带来额外的性能开销。
相比之下,UNION ALL则简单粗暴得多——它直接将所有查询结果拼接在一起,不做任何去重处理。由于少了排序和比较的步骤,UNION ALL的执行效率通常比UNION高得多。
实际经验:在数据仓库ETL过程中,当确定源数据没有重复或允许重复存在时,我总是优先使用UNION ALL,性能通常能提升30%-50%。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 性能差异的底层原理
2.1 UNION的执行流程
- 执行所有参与合并的SELECT语句
- 将结果集放入临时工作区
- 对临时表进行排序操作
- 扫描排序后的结果,移除相邻的重复行
- 返回最终结果
这个过程中,排序操作是最耗资源的步骤,特别是当处理大量数据时。我曾经在一个包含百万级记录的表上测试,UNION比UNION ALL多花了近3倍时间。
2.2 UNION ALL的执行流程
- 执行第一个SELECT语句
- 直接追加第二个SELECT语句的结果
- 继续追加后续SELECT语句的结果
- 返回拼接后的完整结果集
因为没有排序和去重步骤,UNION ALL基本上就是简单的数据搬运,这也是它性能优势的关键所在。
3. 实际应用场景选择指南
3.1 必须使用UNION的情况
- 业务上严格要求结果集不能有重复记录
- 合并来自不同系统的数据,可能存在键值重复
- 做数据分析时需要精确的唯一计数
例如,合并多个分公司的客户名单时,如果同一个客户可能在多个分公司都有记录,这时就该用UNION。
3.2 优先使用UNION ALL的情况
- 确定源数据本身就没有重复
- 允许结果集中存在重复记录
- 处理大数据量且对性能敏感的场景
- 进行ETL过程中的数据抽取阶段
在数据仓库的维度表加载过程中,我通常都会使用UNION ALL,因为源系统已经确保了数据的唯一性。
4. 高级用法与优化技巧
4.1 索引利用策略
当使用UNION时,可以考虑为参与合并的列创建临时索引。我曾经通过这种方式将一个原本需要20分钟的查询优化到3分钟内完成。
4.2 分阶段合并技巧
对于特别大的数据集,可以尝试分批次合并:
sql复制-- 先合并前两个查询
WITH stage1 AS (
SELECT * FROM table1
UNION ALL
SELECT * FROM table2
),
-- 再合并第三个查询
stage2 AS (
SELECT * FROM stage1
UNION ALL
SELECT * FROM table3
)
-- 最后应用UNION去重
SELECT * FROM stage2
UNION
SELECT * FROM table4
4.3 并行处理方案
在大数据平台上,可以结合DISTRIBUTE BY和CLUSTER BY子句实现并行UNION操作。例如在Hive中:
sql复制SELECT * FROM (
SELECT * FROM table1
UNION ALL
SELECT * FROM table2
) t
DISTRIBUTE BY key_column
5. 常见错误与排查指南
5.1 数据类型不匹配错误
UNION要求各查询的列数和数据类型必须兼容。常见的错误包括:
- 列数不一致
- 字符串与数字类型混用
- 日期格式不统一
解决方案是显式进行类型转换:
sql复制SELECT CAST(int_col AS VARCHAR) FROM table1
UNION
SELECT string_col FROM table2
5.2 性能突然下降问题
当UNION查询突然变慢时,检查:
- 参与合并的数据量是否激增
- 临时表空间是否充足
- 服务器内存配置是否合理
我曾遇到一个案例,UNION查询变慢是因为临时表空间不足,导致大量磁盘交换。
5.3 错误的结果集
如果发现UNION结果不符合预期:
- 确认是否误用了UNION ALL
- 检查各查询的WHERE条件
- 验证排序规则是否影响去重
一个实际案例:由于COLLATION设置不同,本应去重的记录被保留了。
6. 各数据库平台的实现差异
6.1 MySQL/MariaDB的特殊性
- 5.7版本前UNION结果默认不排序
- 8.0+版本会按第一列隐式排序
- 可以使用ORDER BY NULL避免排序开销
6.2 SQL Server的优化提示
- 可以使用OPTION (MERGE UNION)提示
- 支持UNION ALL与TOP组合使用
- 2008+版本有更好的并行UNION处理
6.3 Oracle的增强功能
- 支持UNION ALL的PUSH_PRED提示
- 可以结合MATERIALIZE提示优化
- 12c+版本支持UNION ALL的实时物化视图
7. 真实案例:电商数据合并方案
去年我参与了一个电商平台的数据整合项目,需要合并三个系统的订单数据:
sql复制-- 最终采用的方案
WITH combined_orders AS (
-- 主系统订单(确保唯一)
SELECT order_id, customer_id, amount
FROM main_orders
WHERE order_date > '2022-01-01'
UNION ALL
-- 遗留系统1订单(有5%重复)
SELECT order_id, customer_id, amount
FROM legacy_orders1
WHERE NOT EXISTS (
SELECT 1 FROM main_orders
WHERE main_orders.order_id = legacy_orders1.order_id
)
UNION ALL
-- 遗留系统2订单(无重复)
SELECT order_id, customer_id, amount
FROM legacy_orders2
)
-- 最终去重(针对遗留系统1的5%重复)
SELECT DISTINCT order_id, customer_id, amount
FROM combined_orders
这个方案比单纯使用UNION性能提升了60%,因为:
- 主系统数据本身唯一,不需要去重
- 对遗留系统1做了预过滤
- 只在最后一步做必要的去重
8. 进阶:UNION在复杂查询中的应用
8.1 实现全外连接(FULL OUTER JOIN)
在没有直接支持FULL OUTER JOIN的数据库中,可以用UNION模拟:
sql复制-- 左连接部分
SELECT a.*, b.*
FROM table1 a LEFT JOIN table2 b ON a.id = b.id
WHERE b.id IS NULL
UNION ALL
-- 右连接部分
SELECT a.*, b.*
FROM table1 a RIGHT JOIN table2 b ON a.id = b.id
WHERE a.id IS NULL
8.2 分页合并技巧
当需要合并多个查询并分页时,正确的做法是:
sql复制WITH all_data AS (
SELECT col1, col2, 1 as source FROM table1
UNION ALL
SELECT col1, col2, 2 as source FROM table2
)
SELECT * FROM all_data
ORDER BY source, col1
LIMIT 20 OFFSET 40;
8.3 动态条件合并
根据条件选择不同的UNION策略:
sql复制SELECT * FROM (
SELECT col1, col2 FROM table1
WHERE @use_union_all = 1
UNION ALL
SELECT col1, col2 FROM table2
WHERE @use_union_all = 1
UNION
SELECT col1, col2 FROM table1
WHERE @use_union_all = 0
UNION
SELECT col1, col2 FROM table2
WHERE @use_union_all = 0
) t
9. 性能对比测试方法
要准确评估UNION和UNION ALL的性能差异,建议采用以下测试方法:
- 准备测试数据:
sql复制-- 创建包含重复数据的两张表
CREATE TABLE test1 AS
SELECT id, 'data'||mod(id,100) as data
FROM generate_series(1,1000000) id;
CREATE TABLE test2 AS
SELECT id, 'data'||mod(id,50) as data
FROM generate_series(1,1000000) id;
- 执行性能测试:
sql复制-- 测试UNION
EXPLAIN ANALYZE
SELECT * FROM test1
UNION
SELECT * FROM test2;
-- 测试UNION ALL
EXPLAIN ANALYZE
SELECT * FROM test1
UNION ALL
SELECT * FROM test2;
- 分析执行计划:
- 检查是否有排序操作
- 比较实际执行时间
- 观察内存使用情况
在我的测试环境中,100万条记录测试结果:
- UNION ALL: 约800ms
- UNION: 约3500ms
10. 最佳实践总结
经过多年的SQL优化实践,我总结了以下UNION/UNION ALL使用原则:
- 默认优先考虑UNION ALL
- 只有在业务确实需要去重时才用UNION
- 大数据量时考虑分阶段合并
- 注意各查询的列类型一致性
- 为频繁使用的UNION查询创建视图
- 定期监控UNION查询的性能变化
- 在ETL过程中尽量在前端去重
- 考虑使用临时表优化复杂UNION操作
最后分享一个实用技巧:在开发过程中,可以先用UNION ALL快速获取结果,确认数据正确后再根据需要改为UNION,这样能节省大量开发调试时间。
