Hive 的 SMB Join(Sort-Merge-Bucket Join) 是一种高性能的 Map-side Join 优化策略 ,适用于两个大表关联 且满足特定存储条件的场景。它通过预排序 + 分桶 + 合并 的方式,在 Map 阶段完成 Join,避免了昂贵的 Reduce Shuffle,显著提升性能。
一、SMB Join 是什么?
SMB Join = Sort-Merge-Bucket Join
它要求参与 Join 的两个表都:
- 按 Join Key 分桶(Bucketed)
- 在每个桶内按 Join Key 排序(Sorted)
满足条件后,Hive 可以在 Map 端逐桶、逐块地合并数据,无需 Reduce 阶段。
二、SMB Join 的核心前提条件
要启用 SMB Join,必须同时满足以下条件:
| 条件 | 说明 |
|---|---|
| ✅ 两张表都是分桶表 | CLUSTERED BY (join_key) INTO N BUCKETS |
| ✅ 两张表在桶内按 Join Key 排序 | SORTED BY (join_key) |
| ✅ 桶数量成倍数关系 | 如 A 表 32 桶,B 表 16 桶(32 % 16 == 0) |
| ✅ Join Key 与分桶/排序字段一致 | 必须是同一个字段(或字段组合) |
| ✅ 开启 SMB Join 参数 | set hive.optimize.bucketmapjoin = true; set hive.optimize.bucketmapjoin.sortedmerge = true; |
⚠️ 如果不满足,Hive 会回退到普通 MapJoin 或 Common Join(Shuffle + Reduce)。
三、SMB Join 执行原理(Map 端合并)
场景示例:
sql
-- 表 orders(32 桶,按 user_id 排序)
CREATE TABLE orders (user_id INT, order_amt DECIMAL)
CLUSTERED BY (user_id) SORTED BY (user_id) INTO 32 BUCKETS;
-- 表 users(16 桶,按 user_id 排序)
CREATE TABLE users (user_id INT, name STRING)
CLUSTERED BY (user_id) SORTED BY (user_id) INTO 16 BUCKETS;
-- 查询
SELECT u.name, o.order_amt
FROM users u
JOIN orders o ON u.user_id = o.user_id;
执行过程:
步骤 1️⃣:任务划分
- Hive 启动 N 个 Map Task,其中 N = max(桶数A, 桶数B) = 32;
- 每个 Map Task 负责处理 一组对应的桶 :
- Map Task 0:处理
users_bucket_0和orders_bucket_0 - Map Task 1:处理
users_bucket_1和orders_bucket_1 - ...
- Map Task 15:处理
users_bucket_15和orders_bucket_15 - Map Task 16~31:只处理
orders_bucket_16~31(users 无对应桶,但因 32%16=0,可映射)
- Map Task 0:处理
🔁 关键 :由于桶数成倍数,每个 orders 桶都能找到唯一的 users 桶(通过
bucket_id % min_bucket_count映射)。
步骤 2️⃣:桶内有序合并(Merge)
- 每个 Map Task 同时读取两个桶文件;
- 因为两个文件都按 user_id 排序 ,可使用 归并排序(Merge Join)算法 :
- 用两个指针分别遍历两个文件;
- 比较当前 user_id:
- 相等 → 输出 Join 结果;
- A < B → 移动 A 指针;
- A > B → 移动 B 指针;
- 全程无需加载全表到内存,流式处理。
步骤 3️⃣:直接输出结果
- Join 结果由 Map Task 直接写入 HDFS;
- 没有 Reduce 阶段,无 Shuffle 网络传输!
四、SMB Join vs 其他 Join 方式对比
| Join 类型 | 适用场景 | 是否需要 Reduce | 内存要求 | 性能 |
|---|---|---|---|---|
| Common Join | 任意表 | ✅ 是 | 低 | 慢(Shuffle 开销大) |
| MapJoin | 一张小表(<25MB) | ❌ 否 | 小表需全加载内存 | 快 |
| Bucket MapJoin | 两张分桶表(不要求排序) | ❌ 否 | 每个桶需加载内存 | 较快 |
| SMB Join | 两张大表,分桶+排序 | ❌ 否 | 极低(流式 merge) | 最快 |
✅ SMB 是 唯一能高效处理两个大表 Join 且无 Reduce 的方案。
五、如何创建支持 SMB Join 的表?
建表示例:
sql
-- 开启强制分桶(Hive 2.x+ 需设置)
SET hive.enforce.bucketing = true;
SET hive.enforce.sorting = true;
-- 创建分桶+排序表
CREATE TABLE smb_table (
id INT,
value STRING
)
CLUSTERED BY (id)
SORTED BY (id)
INTO 32 BUCKETS
STORED AS ORC; -- 推荐列式存储
插入数据(必须用 INSERT OVERWRITE):
sql
INSERT OVERWRITE TABLE smb_table
SELECT id, value
FROM source_table
CLUSTER BY id; -- 注意:用 CLUSTER BY 而非 DISTRIBUTE BY + SORT BY
⚠️
CLUSTER BY col=DISTRIBUTE BY col SORT BY col,确保分桶和排序同时生效。
六、验证是否触发 SMB Join
执行 EXPLAIN 查看执行计划:
sql
EXPLAIN
SELECT ...
FROM table1 t1
JOIN table2 t2 ON t1.key = t2.key;
若看到:
Map Operator Tree:
TableScan
Select Operator
SortMergeJoin Operator <-- 关键标识!
说明 SMB Join 已生效。
七、注意事项 & 限制
- 仅支持等值 Join (
ON a.id = b.id),不支持<,LIKE等; - Join Key 必须与分桶/排序字段完全一致;
- 桶数必须成倍数,否则无法一一对应;
- 建表和插入必须严格遵循分桶+排序规则;
- 不适合频繁更新的表(分桶结构难以维护);
- Hive 3.x+ 对 ACID 表支持有限,建议用于静态维度表或事实表。
八、总结
| 特性 | 说明 |
|---|---|
| 核心思想 | 利用预计算(分桶+排序)将 Join 转为高效的流式 Merge |
| 最大优势 | 无 Shuffle、无 Reduce、低内存、高吞吐 |
| 适用场景 | 两个大表按相同 Key 分桶排序后的等值 Join |
| 性能提升 | 相比 Common Join 可提速 3~10 倍 |
| 使用门槛 | 需提前规划表结构,ETL 流程需配合 |
💡 SMB Join 是 Hive 数仓中"用存储换计算"的经典范例 。
虽然建表和数据导入稍复杂,但在高频大表关联场景下,收益巨大,值得投入。