GBase 8a UNION ALL 与多路合并:少洗牌、少落盘的写法
业务里经常要把多张结构相近的表「摞在一起」:按日分表的历史订单、多源入库的明细、或 ETL 中间结果汇总。第一反应是 UNION ALL,在单机库里往往够用;在 GBase 8a 的 MPP 里,合并方式会决定要不要 REDISTRIBUTE、会不会在 CN 上 Gather 超大结果集,性能可以差一个数量级。
本文说明 UNION ALL / UNION 在 8a 上的常见计划形态、与分布键的关系,以及更稳妥的合并写入路径。
一、UNION ALL 与 UNION:语义差一步,计划差一截
-- 分支 A:保留重复行,通常更利于优化器下推
SELECT id, amt, dt FROM orders_20260501
UNION ALL
SELECT id, amt, dt FROM orders_20260502;
-- 分支 B:去重,往往要多一轮 Sort/Distinct
SELECT id, amt, dt FROM orders_20260501
UNION
SELECT id, amt, dt FROM orders_20260502;
| 写法 | 语义 | 8a 上常见代价 |
|---|---|---|
| UNION ALL | 直接拼接 | 若两侧已同分布,可能本地 Append |
| UNION | 去重合并 | 常伴随 Distinct/Sort,易落盘 |
原则:业务允许重复时一律优先 UNION ALL;必须去重时,评估是否能在各分片先 GROUP BY 再合并,而不是对大结果集做一次全局 DISTINCT。
二、分布键:合并前两表是否「同分布」
若 orders_20260501 与 orders_20260502 都是 DISTRIBUTED BY (id),且 SELECT 的列与分布键兼容,优化器更可能让各 DN 本地拼接,Motion 较少。
反例:
- 表 A 按
idHASH,表 B 按dtHASH,却UNION ALL后按idJoin 大表 → 合并后往往要 REDISTRIBUTE 整坨数据。 - 一侧是复制表、一侧是 HASH 表,合并键与分布键不一致 → 广播或洗牌二选一,都可能很贵。
合并前用 EXPLAIN 看计划(示例):
EXPLAIN
SELECT id, SUM(amt) FROM (
SELECT id, amt FROM orders_20260501
UNION ALL
SELECT id, amt FROM orders_20260502
) t
GROUP BY id;
标注是否出现:
- 子查询上的 REDISTRIBUTE
- Append 是否在各 DN 本地完成
GROUP BY id能否 本地聚合(分布键 =id时更优)
三、多表 UNION ALL 的正确姿势
1. 列类型与顺序对齐
MPP 对列宽、字符集、decimal 精度敏感。合并前统一:
- 列顺序、别名一致;
- 避免一侧
VARCHAR(20)、另一侧VARCHAR(200)隐式放大; - 日期类型一致,勿一侧
DATE一侧DATETIME混用导致函数包裹,影响裁剪。
2. 先过滤再 UNION
SELECT id, amt FROM orders_20260501 WHERE dt = DATE '2026-06-01'
UNION ALL
SELECT id, amt FROM orders_20260502 WHERE dt = DATE '2026-06-01';
比「先 UNION 全表再 WHERE」少搬无数行。分表场景下,谓词下推到每个分支 是免费午餐。
3. 超多分支:考虑中间表或 gload
数十个 UNION ALL 嵌套会让计划复杂、编译变慢。更稳:
- 各源 gload 到同分布键的 staging 表;
- 或
INSERT INTO target SELECT ... FROM branch_i循环(注意事务大小); - 最后对 target 做一次
ANALYZE。
四、合并写入目标表:分布键要一次设计对
常见需求:多日分表合并进一张总表 orders_all,按 id 查询。
CREATE TABLE orders_all (
id BIGINT,
amt DECIMAL(18,2),
dt DATE
) DISTRIBUTED BY ('id');
INSERT INTO orders_all
SELECT id, amt, dt FROM orders_20260501
UNION ALL
SELECT id, amt, dt FROM orders_20260502;
若 orders_all 按 dt 分布而查询按 id,合并完成后业务 Join 仍会洗牌。建表时应对齐 最高频 Join/Group 键。
大批量 INSERT ... SELECT ... UNION ALL 在高峰可能占满 IO;建议维护窗口、分批 INSERT,并监控 DN 的 tmp(参见大查询落盘文)。
五、去重合并的替代路径
必须全局去重时,可对比:
路径 A:UNION(简单但计划可能重)
路径 B:分阶段
INSERT INTO staging SELECT id, amt, dt FROM ... UNION ALL ...;
-- staging 与目标表同分布键 id
INSERT INTO orders_dedup
SELECT id, amt, dt FROM staging
GROUP BY id, amt, dt;
路径 C:若去重键就是分布键,各分片 GROUP BY 后再 UNION ALL,减少全局 Distinct 数据量。
对每种路径跑 EXPLAIN,选 Motion 最少、预估行数最可信的一种(配合 ANALYZE)。
六、案例:分表 UNION 后 Join 变慢
场景:12 张按月分表,UNION ALL 后 Join 维表 users,合并子查询未下推过滤,单月查询却扫 12 表全量。
计划现象:巨大 Append → REDISTRIBUTE → Hash Join。
改法:
- 应用层只
UNION当月分表,或动态 SQL 只访问orders_202606; - 维表小则改 REPLICATED(容量允许);
- 合并结果写入按
user_id分布的中转表,再 Join。
改后应看到分支减少、REDISTRIBUTE 行数下降一个数量级。
小结
GBase 8a 上做多路合并,优先 UNION ALL + 分支下推 + 分布键对齐;慎用对大结果集的 UNION 去重。用 EXPLAIN 盯 Append 后是否跟大 REDISTRIBUTE,大批量合并考虑 staging/gload。把「合并前后计划、行数、耗时」记下来,团队以后看到 UNION 嵌套会先问分布键,而不是一味加索引。
评论
热门帖子
- 12025-12-01浏览数:183265
- 22023-05-09浏览数:26036
- 42023-09-25浏览数:19689
- 52020-05-11浏览数:18321