Skip to main content

为什么选择 ClickHouse Cloud 而非 BigQuery?

简而言之:在现代数据分析场景中,ClickHouse 比 BigQuery 更快、更便宜,功能也更强大:

将 BigQuery 数据加载到 ClickHouse Cloud

数据集

作为展示从 BigQuery 迁移到 ClickHouse Cloud 的典型示例数据集,我们使用了 这里 介绍的 Stack Overflow 数据集。该数据集包含 Stack Overflow 自 2008 年至 2024 年 4 月期间的所有 post、vote、user、comment 和 badge 数据。该数据的 BigQuery schema 如下所示: 对于希望将此数据集导入 BigQuery instance 以测试迁移步骤的用户,我们已在一个 GCS 存储桶中提供了这些表的 Parquet 格式数据,并在 这里 提供了用于在 BigQuery 中创建和加载这些表的 DDL 命令。

迁移数据

BigQuery 与 ClickHouse Cloud 之间的数据迁移主要分为两类工作负载:
  • 初始批量加载并定期更新 - 需要先迁移初始数据集,再按固定时间间隔 (例如每天) 定期更新。这里的更新通过重新发送已发生变化的行来完成——这些变化可通过某个可用于比对的列 (例如日期列) 来识别。删除操作则通过定期完整重载整个数据集来处理。
  • 实时复制或 CDC (变更数据捕获) - 需要先迁移初始数据集。之后,对该数据集的变更必须近实时地同步到 ClickHouse,且只允许数秒的延迟。实际上,这就是一个 变更数据捕获 (CDC (变更数据捕获) ) 流程:BigQuery 中的表必须与 ClickHouse 保持同步,也就是说,BigQuery 表中的插入、更新和删除都必须应用到 ClickHouse 中对应的表。

通过 Google Cloud Storage (GCS) 进行批量加载

BigQuery 支持将数据导出到 Google 的对象存储 (GCS) 。以我们的示例数据集为例:
  1. 将这 7 个表导出到 GCS。相关命令可在此处查看。
  2. 将数据导入 ClickHouse Cloud。为此,可以使用 gcs 表函数。DDL 和导入查询可在此处查看。请注意,由于 ClickHouse Cloud 实例由多个计算节点组成,因此这里使用的是 s3Cluster 表函数,而不是 gcs 表函数。该函数同样适用于 gcs 存储桶,并且会利用 ClickHouse Cloud 服务的所有节点并行加载数据。
这种方法有多项优势: 在尝试下面的示例之前,建议先查看导出所需的权限和位置建议,以尽可能提升导出和导入性能。

通过计划查询实现实时复制或 CDC

CDC (变更数据捕获) 是让两个数据库中的表保持同步的过程。如果要以近实时方式处理更新和删除,复杂度会显著增加。一种方法是直接利用 BigQuery 的计划查询功能定期导出数据。只要你能接受数据写入 ClickHouse 时存在一定延迟,这种方法就很容易实现和维护。示例见这篇博文。

设计 schema

Stack Overflow 数据集包含多个相关的表。我们建议先重点迁移主表。它不一定是最大的表,而是你预计会承载最多分析查询的那张表。这样可以帮助你熟悉 ClickHouse 的核心概念。随着添加更多表,为了充分利用 ClickHouse 的功能并获得最佳性能,这张表可能还需要重新建模。我们在 数据建模文档 中进一步介绍了这一建模过程。 遵循这一原则,我们将重点放在主要的 posts 表上。其 BigQuery schema 如下所示:

优化类型

按照此处所述的流程操作后,会得到如下 schema:
我们可以通过一个简单的 INSERT INTO SELECT 来填充这张表,使用 gcs table function 从 gcs 读取导出的数据。请注意,在 ClickHouse Cloud 上,你也可以使用与 gcs 兼容的 s3Cluster table function,以便在多个节点间并行加载:
在新的 schema 中,我们不保留任何 null 值。上述插入操作会将这些值隐式转换为其各自类型的默认值——整数为 0,字符串为空字符串。ClickHouse 还会自动将所有数值转换为其目标精度。

ClickHouse 主键有何不同?

如此处所述,与 BigQuery 一样,ClickHouse 不会强制表的主键列值保持唯一。 与 BigQuery 中的聚簇类似,ClickHouse 表中的数据会按主键列排序后存储在磁盘上。查询优化器会利用这种排序顺序来避免额外排序、减少 JOIN 的内存占用,并对 limit 子句进行短路处理。 不同于 BigQuery,ClickHouse 会根据主键列值自动创建 (稀疏) 主索引。该索引用于加速所有包含主键列过滤条件的查询。具体来说:
  • 对于 ClickHouse 常见的使用规模而言,内存和磁盘效率至关重要。数据会以称为 parts 的块写入 ClickHouse 表,并按照规则在后台合并这些 parts。在 ClickHouse 中,每个 part 都有自己的主索引。当 parts 被合并时,合并后 part 的主索引也会一并合并。需要注意的是,这些索引并不是为每一行单独构建的。相反,一个 part 的主索引会为每组行保存一个索引条目——这种技术称为稀疏索引。
  • 稀疏索引之所以可行,是因为 ClickHouse 会按指定键对 part 中的行排序后存储到磁盘上。稀疏主索引不会像基于 B-Tree 的索引那样直接定位到单行,而是让 ClickHouse 能够快速地 (通过对索引条目进行 binary search) 识别出可能匹配查询的行组。随后,这些可能匹配的行组会被并行流式传输到 ClickHouse engine 中,以查找真正匹配的行。这种索引设计使主索引可以非常小 (可完全装入主内存) ,同时仍能显著提升查询执行速度,尤其适合数据分析场景中常见的范围查询。更多细节,建议阅读这篇深入指南。
在 ClickHouse 中,所选主键不仅决定索引,还决定数据写入磁盘时的排列顺序。因此,它会显著影响压缩效果,而压缩效果又会进一步影响查询性能。如果某个排序键能让大多数列的值以连续顺序写入,那么所选的压缩算法 (以及编解码器) 就能更高效地压缩数据。
表中的所有列都会根据指定排序键的值进行排序,无论这些列本身是否包含在该键中。比如,如果使用 CreationDate 作为键,那么其他所有列中的值顺序都会与 CreationDate 列中的值顺序保持一致。也可以指定多个排序键——其排序语义与 SELECT 查询中的 ORDER BY 子句相同。

如何选择排序键

有关选择排序键时的考量因素和步骤,请参见此处,其中以 posts 表为例。

数据建模技术

我们建议从 BigQuery 迁移而来的用户阅读ClickHouse 数据建模指南。本指南使用相同的 Stack Overflow 数据集,并介绍了利用 ClickHouse 特性的多种方法。

分区

如果你使用过 BigQuery,那么你一定熟悉表分区这一概念:通过将表拆分为称为分区的更小、更易管理的部分,可以提升大型数据库的性能和可管理性。这种分区既可以基于指定列上的范围 (例如日期) 来实现,也可以使用预定义列表,或通过对某个键进行哈希来实现。这样,管理员就可以根据日期范围或地理位置等特定条件来组织数据。 分区有助于通过分区裁剪和更高效的索引来提升查询性能,从而加快数据访问速度。它还便于执行备份和数据清理等维护任务,因为这些操作可以针对单个分区进行,而不必处理整张表。此外,分区还可以通过将负载分散到多个分区上,显著提升 BigQuery 数据库的可扩展性。 在 ClickHouse 中,分区是在表初始定义时通过 PARTITION BY 子句指定的。该子句可以包含基于任意列的 SQL 表达式,其结果将决定某一行会被发送到哪个分区。 磁盘上的数据分区片段在逻辑上都隶属于各自的分区,并且可以单独查询。在下面的示例中,我们使用表达式 toYear(CreationDate) 按年份对 posts 表进行分区。随着行被插入 ClickHouse,系统会对每一行计算该表达式的结果——随后,这些行会以属于相应分区的新数据分区片段的形式被写入对应分区。

应用场景

ClickHouse 中的分区与 BigQuery 中的应用场景类似,但也有一些细微差别。更具体地说:
  • 数据管理 - 在 ClickHouse 中,应主要将分区视为一种数据管理功能,而不是查询优化技术。通过基于某个键对数据进行逻辑划分,每个分区都可以独立操作,例如删除。这使你能够按时间高效地在存储层级之间移动分区及其子集,或让数据过期/从集群中高效删除数据。例如,在下面的示例中,我们会删除 2008 年的帖子数据:
  • 查询优化 - 虽然分区可以帮助提升查询性能,但效果在很大程度上取决于访问模式。如果查询只涉及少量分区 (理想情况下仅一个) ,性能可能会有所提升。通常只有在分区键不在主键中,且查询会按该键进行过滤时,这才比较有意义。不过,如果查询需要覆盖很多分区,性能反而可能比不分区更差 (因为分区可能会导致产生更多 parts) 。如果分区键本身已经是主键中靠前的字段,那么只命中单个分区带来的收益会进一步降低,甚至几乎没有。若每个分区中的值都是唯一的,分区还可用于优化 GROUP BY 查询。不过,一般来说,你应优先确保主键设计已优化,只有在少数特殊场景下,才应将分区作为查询优化手段来考虑——即访问模式会稳定地访问某个可预测的特定时间子集,例如按天分区,而大多数查询都集中在最近一天。

建议

你应将分区视为一种数据管理技术。在处理时间序列数据时,如果需要让数据从集群中过期清除,它尤其适用。例如,最旧的分区可以直接删除。 重要提示:确保你的分区键表达式不会产生高基数集合,也就是说,应避免创建超过 100 个分区。例如,不要按客户端标识符或名称等高基数字段对数据进行分区。相反,应将客户端标识符或名称作为 ORDER BY 表达式中的第一列。
在内部,ClickHouse 会为插入的数据创建 parts。随着插入数据不断增多,parts 的数量也会增加。为防止 parts 数量过多而降低查询性能 (因为需要读取更多文件) ,系统会在后台通过异步过程将 parts 合并。如果 parts 的数量超过了预先配置的限制,ClickHouse 就会在插入时抛出异常,并报出“parts 过多”错误。正常运行时不应出现这种情况;它通常只会在 ClickHouse 配置不当或使用方式不正确时发生,例如存在大量小批量插入。由于 parts 是按分区彼此独立创建的,分区数量增加也会导致 parts 的数量增加,也就是说,parts 数量会随分区数成倍增长。因此,高基数分区键可能会导致此错误,应尽量避免。

materialized view 与投影

ClickHouse 中的投影概念允许你为同一张表指定多个 ORDER BY 子句。 在 ClickHouse 数据建模 中,我们探讨了如何在 ClickHouse 中使用 materialized view 预先计算聚合、转换行,以及针对不同的访问模式优化查询。对于后者,我们给出了一个示例:materialized view 会将行发送到一个目标表,而该表使用的排序键与接收插入的原始表不同。 例如,考虑以下查询:
此查询需要扫描全部 9000 万行 (虽然速度很快) ,因为 UserId 不是排序键。此前,我们通过一个充当 PostId 查找表的 materialized view 来解决这个问题。这个问题同样也可以用投影来解决。 下面的命令会添加一个使用 ORDER BY user_id 的投影。
请注意,我们必须先创建投影,然后再将其物化。 后一个命令会使数据在磁盘上以两种不同的 顺序各存储一份。投影也可以在创建数据时一并定义,如下所示, 并且会在插入数据时自动维护。
如果该投影是通过 ALTER 命令创建的,那么在执行 MATERIALIZE PROJECTION 命令后, 创建过程会异步进行。你可以使用以下查询来确认此操作的进度, 并等待 is_done=1。
如果再次执行上述查询,可以看到性能显著提升, 但代价是需要额外的存储空间。
通过 EXPLAIN 命令,我们还可以确认此查询使用了该投影:

何时使用投影

对于新用户来说,投影是一项很有吸引力的功能,因为它会在数据插入时自动维护。 此外,查询只需发送到单个表,在可能的情况下就会利用投影来加快响应 时间。 这与 materialized view 不同;对于后者,用户必须根据过滤器 选择合适的优化目标表,或者重写查询。 这会对用户应用程序提出更高要求,并增加客户端侧的 复杂性。 尽管有这些优势,投影也存在一些固有的局限性, 您需要了解这些局限,因此应谨慎使用。更多 详情请参见”materialized view 与投影” 我们建议在以下情况下使用投影:
  • 需要对数据进行完全重排序。虽然投影中的表达式理论上可以使用 GROUP BY,,但 materialized view 在维护聚合方面更高效。查询优化器也更可能利用采用简单重排序的投影,即 SELECT * ORDER BY x。您可以在该表达式中只选择部分列,以减少存储占用。
  • 用户能够接受随之而来的存储占用增加,以及数据被写入两次带来的开销。请测试其对插入速度的影响,并评估存储开销。

在 ClickHouse 中改写 BigQuery 查询

下面给出一些对比 BigQuery 与 ClickHouse 的示例查询。此列表旨在展示如何利用 ClickHouse 的特性大幅简化查询。这里的示例使用的是完整的 Stack Overflow 数据集 (截至 2024 年 4 月) 。 Users (提问数超过 10 个) 中浏览量最高的用户: BigQuery ClickHouse
哪些标签的浏览量最高: BigQuery
ClickHouse

聚合函数

在条件允许的情况下,应尽量利用 ClickHouse 的聚合函数。下面我们以 argMax 函数 为例,说明如何计算每年浏览量最高的问题。 BigQuery ClickHouse

条件判断与数组

条件函数和数组函数能显著简化查询。以下查询计算出从 2022 年到 2023 年间增幅百分比最大的标签 (出现次数超过 10000 次) 。请注意,借助条件判断、数组函数,以及在 HAVING 和 SELECT 子句中复用别名的能力,下面的 ClickHouse 查询写得非常简洁。 BigQuery ClickHouse
如果你正从 BigQuery 迁移到 ClickHouse,我们的基础指南到这里就告一段落了。建议继续阅读在 ClickHouse 中进行数据建模指南,进一步了解 ClickHouse 的高级功能。
最后修改于 2026年6月10日