ClickHouse JOIN 的特点
ClickHouse 的 JOIN 支持 INNER、LEFT、RIGHT、FULL 以及按时间就近匹配的 ASOF,还有只取一行的 ANY 变体。它的执行方式与关系库不同:右表通常会被加载进内存再做匹配,所以「小表放右边、大表放左边」是控制资源的主要手段;分布式表上还要分清本地与全局语义。
基本信息
| 常用类型 | INNER、LEFT、RIGHT、FULL、CROSS、ASOF(按最近时间匹配) |
|---|---|
| ANY 语义 | LEFT ANY JOIN 在右表有多条匹配时只取一行,避免结果行数膨胀 |
| 分布式行为 | GLOBAL JOIN 先把右表汇总再分发,普通 JOIN 在每个分片本地执行 |
| 执行方式 | 右表常被加载进内存,内存不足时可能报错或落盘 |
| 版本注意 | 早期版本 JOIN 能力有限,升级前先确认语法支持情况 |
典型使用场景
- 宽表补维度:给事实表补上用户或商品名称
- 按时间就近匹配:取事件发生前最近一次配置或价格
- 事实表关联维表后再聚合,做多维度统计
- 把 IN 子查询改写成 JOIN,换取更可控的执行计划
- 本地表与外部引擎表关联,做跨系统数据拼接
常用写法
常用写法
-- 常规左连接:补上用户名称
SELECT e.ts, e.user_id, u.name
FROM events AS e
LEFT JOIN users AS u ON e.user_id = u.id
WHERE e.ts >= today();
-- 按时间就近匹配:取插入时刻前最近一次配置
SELECT e.ts, e.id, c.cfg_version
FROM events AS e
ASOF LEFT JOIN configs AS c
ON e.id = c.id AND e.ts >= c.ts;
-- 右表多条匹配时只取一行,避免结果行数膨胀
SELECT e.id, u.name
FROM events AS e
LEFT ANY JOIN users AS u ON e.user_id = u.id;
-- 分布式环境下需要全局语义时使用 GLOBAL JOIN
SELECT count()
FROM dist_events AS e
GLOBAL LEFT JOIN dist_users AS u ON e.user_id = u.id;JOIN 变体与取舍
| 写法 | 语义 | 注意 |
|---|---|---|
| INNER JOIN | 两边都存在才返回 | 结果变少时要确认是否该用 LEFT |
| LEFT ANY JOIN | 右表多匹配只取一行 | 避免膨胀,但取哪一行不确定 |
| ASOF JOIN | 匹配最近一行满足条件的记录 | 用于时间序列式的补值 |
| GLOBAL JOIN | 右表先汇总再分发到各分片 | 分布式查询的正确性保障,代价更高 |
多表 JOIN 时把行数少的表按顺序放在右侧;复杂查询可以先用子查询把两张大表各自缩小到必要范围,内存峰值会明显下降。
右表超过可用内存时会报错或落到磁盘,join_algorithm 与内存相关设置要按数据量调整;数据量远超内存时,更稳的做法是先把小表物化成字典或预聚合结果。