- 表很少发生变化,或者可以接受批量刷新。
- 关系不是多对多,或者基数不会过高。
- 实际会查询的列只有一小部分,也就是说,某些列可以不纳入反规范化。
- 你具备将处理工作从 ClickHouse 转移到上游系统 (如 Flink) 的能力,在那里可以管理实时富集或扁平化。
何时需要 JOIN
- 避免笛卡尔积:如果左侧的某个值与右侧的多个值匹配,JOIN 将返回多行——这就是所谓的笛卡尔积。如果你的使用场景并不需要右侧所有匹配项,只需要其中任意一个匹配项,可以使用
ANYJOIN (例如LEFT ANY JOIN) 。与常规 JOIN 相比,这类 JOIN 更快且占用更少内存。 - 减小参与 JOIN 的表规模:JOIN 的运行时间和内存消耗会随着左右两张表的大小成比例增长。要减少 JOIN 处理的数据量,请在查询的
WHERE或JOIN ON子句中添加额外的过滤条件。ClickHouse 会尽可能将过滤条件下推到查询计划的更深层,通常会在 JOIN 之前执行。如果过滤条件由于某种原因没有被自动下推,可以将 JOIN 的一侧改写为子查询,以强制进行下推。 - 在适用时通过字典使用 direct JOIN:ClickHouse 中的标准 JOIN 分两个阶段执行:首先是构建阶段,遍历右侧并构建哈希表;然后是探测阶段,遍历左侧,并通过哈希表查找匹配的 JOIN 对象。如果右侧是字典或另一个具有键值特征的表引擎 (例如 EmbeddedRocksDB 或 Join table engine) ,那么 ClickHouse 可以使用 “direct” JOIN 算法,从而实际上无需构建哈希表,加快查询处理速度。它适用于
INNER和LEFT OUTERJOIN,在实时分析类工作负载中是更推荐的选择。 - 利用表排序优化 JOIN:ClickHouse 中的每张表都会按照表的主键列排序。可以通过使用所谓的 sort-merge JOIN 算法 (例如
full_sorting_merge和partial_merge) 来利用这种排序。与基于哈希表的标准 JOIN 算法 (见下文parallel_hash、hash、grace_hash) 不同,sort-merge JOIN 算法会先对两张表排序,再进行合并。如果查询是基于两张表各自的主键列进行 JOIN,那么 sort-merge 有一项优化可以省略排序步骤,从而节省处理时间和开销。 - 避免发生落盘的 JOIN:JOIN 的中间状态 (例如哈希表) 可能会变得非常大,以至于无法装入主内存。在这种情况下,ClickHouse 默认会返回内存不足错误。某些 join 算法 (见下文) ,例如
grace_hash、partial_merge和full_sorting_merge,能够将中间状态落盘并继续执行查询。不过,这些 join 算法仍应谨慎使用,因为磁盘访问会显著拖慢 join 处理。我们建议优先通过其他方式优化 JOIN 查询,以减小中间状态的规模。 - 在 outer JOIN 中使用默认值作为未匹配标记:左/右/全外连接会包含左表/右表/两张表中的所有值。如果某个值在另一张表中找不到对应的 JOIN 对象,ClickHouse 会用一个特殊标记来替代该 JOIN 对象。SQL 标准要求数据库使用 NULL 作为这种标记。在 ClickHouse 中,这要求将结果列包装为 Nullable,从而带来额外的内存和性能开销。作为替代方案,你可以配置设置
join_use_nulls = 0,并使用结果列数据类型的默认值作为标记。
谨慎使用字典在 ClickHouse 中使用字典进行 JOIN 时,需要注意:按照设计,字典不允许出现重复键。在数据加载过程中,任何重复键都会被静默去重——对于同一个键,只保留最后加载的值。这一特性使字典非常适合一对一或多对一的关系,也就是只需要最新值或权威值的场景。但如果将字典用于一对多或多对多关系 (例如,将角色连接到演员,而一个演员可以有多个角色) ,就会导致静默的数据丢失,因为除其中一行外,其他所有匹配行都会被丢弃。因此,字典并不适用于需要在多条匹配记录之间完整保留关系的场景。有关字典适用与不适用场景的更多说明,请参见字典最佳实践。
选择正确的 JOIN 算法
- Parallel Hash JOIN (默认) : 适用于能装入内存的中小型右侧表,速度很快。
- Direct JOIN: 使用字典 (或其他具有键值特性的表引擎) 并配合
INNER或LEFT ANY JOIN时尤为理想——它无需构建哈希表,因此是点查找最快的方法。 - Full Sorting Merge JOIN: 当两张表都按连接键排序时,效率很高。
- Partial Merge JOIN: 可将内存占用降到最低,但速度较慢——最适合在内存有限时连接大型表。
- Grace Hash JOIN: 灵活且可调节内存使用,适合大型数据集,并可按需权衡性能表现。
每种算法支持的 JOIN 类型各不相同。每种算法所支持 JOIN 类型的完整列表可在这里查看。
join_algorithm = 'auto' (默认值) 让 ClickHouse 自动选择最佳算法,也可以根据你的工作负载显式指定。如果你需要为优化性能或内存开销而选择 JOIN 算法,我们建议参考本指南。
为了获得最佳性能:
- 在高性能工作负载中尽量减少 JOIN。
- 每个查询应避免使用超过 3–4 个 JOIN。
- 基于真实数据对不同算法进行基准测试——性能会因 JOIN 键分布和数据规模而异。