JOIN,并提供多种 JOIN 算法可供选择。为获得最佳性能,我们建议遵循本指南中列出的 JOIN 优化建议。
- 为了获得最佳性能,应尽量减少查询中的
JOIN数量,尤其是在要求毫秒级响应的实时分析工作负载中。建议将单个查询中的JOIN数量控制在 3 到 4 个以内。我们在数据建模部分中详细介绍了多种减少 JOIN 的方法,包括反规范化、字典和 materialized views。 - 从 ClickHouse 24.12 开始,查询规划器会自动调整双表 JOIN 的顺序,将较小的表放在右侧以获得最佳性能。到了 25.9 版本,这一能力进一步扩展到可优化包含三个或更多表的查询的 JOIN 顺序。
- 如果你的查询需要直接 JOIN,即
LEFT ANY JOIN(如下所示) ,我们建议在可能的情况下使用字典。
- 如果执行的是 inner join,通常将其改写为使用
IN子句的子查询会更高效。请看下面这些功能等价的查询。它们都会找出那些在问题中未提到 ClickHouse、但在comments中提到的posts数量。
ANY INNER JOIN,而不是普通的 INNER JOIN,因为我们不希望产生笛卡尔积;也就是说,我们希望每篇帖子只匹配一条记录。
这个 join 也可以改写为子查询,从而显著提升性能:
JOIN 的数据量。请看下面的示例,我们想计算自 2020 年以来与 Java 相关的帖子获得的赞成票数量。
一个未经优化的查询,将较大的表放在左侧,执行完成耗时 56 秒:
INNER JOIN 移到子查询中,并在外层查询和内层查询中都保留过滤器后,此查询还可以进一步优化。
选择 JOIN 算法
这些算法决定了 JOIN 查询的规划与执行方式。默认情况下,ClickHouse 会根据所使用的 JOIN 类型、strictness,以及被连接表的引擎,选择 direct 或 hash join 算法。此外,也可以将 ClickHouse 配置为在运行时根据资源可用性和使用情况,自适应地选择并动态切换 JOIN 算法:当
join_algorithm=auto 时,ClickHouse 会先尝试 hash join 算法;如果该算法超出内存限制,则会在运行过程中切换为 partial merge join。你可以通过 trace 日志查看实际选择了哪种算法。ClickHouse 还允许你通过 join_algorithm 设置自行指定所需的 JOIN 算法。
下方展示了各 JOIN 算法支持的 JOIN 类型,优化前应先加以考虑:
关于各个
JOIN 算法的完整详细说明可见此处,其中包括它们的优点、缺点以及扩展性特征。
选择合适的 JOIN 算法,取决于你希望优先优化内存使用还是性能。
优化 JOIN 性能
-
(1) 如果右侧表中的数据可以预先加载到内存中的低延迟键值数据结构中 (例如字典) ,并且 连接键 与底层键值存储的键属性匹配,同时
LEFT ANY JOIN语义也能满足需求,那么就可以使用 direct join,这是速度最快的方案。 - (2) 如果表的物理行顺序与 连接键 的排序顺序一致,那么就要具体情况具体分析了。在这种情况下,full sorting merge join 会跳过排序阶段,从而显著降低内存使用;此外,具体执行速度还取决于数据量以及 连接键 值的分布,在某些情况下它甚至会比某些 hash join 算法更快。
- (3) 如果右表能够放入内存,即使算上 parallel hash join 的额外内存开销,那么这种算法或 hash join 可能会更快。这取决于数据量、数据类型以及连接键列的值分布。
- (4) 如果右表无法放入内存,那么同样还是要视情况而定。ClickHouse 提供了三种不受内存限制的 JOIN 算法。这三种算法都会临时将数据落盘。Full sorting merge join 和 partial merge join 需要先对数据进行排序,而 grace hash join 则会改为基于数据构建 hash table。根据数据量、数据类型以及连接键列的值分布,在某些场景下,基于数据构建 hash table 会比对数据排序更快;反之亦然。
针对内存进行优化
- (1) 如果表的物理行顺序与连接键的排序顺序一致,那么 full sorting merge join 的内存占用可以降到最低。另一个好处是 join 速度也不错,因为排序阶段会被禁用。
- (2) grace hash join 可以通过配置较多的桶来调优到极低的内存占用,但代价是 join 速度会下降。partial merge join 则是专门设计为只使用少量主内存。启用外部排序的 full sorting merge join 通常会比 partial merge join 使用更多内存 (前提是行顺序与连接键排序顺序不一致) ,但优势是 join 执行时间会显著更短。