跳转到主要内容
我们建议在 ClickHouse 中处理 Iceberg 数据时使用 Iceberg 表函数。Iceberg 表函数 目前已具备足够的功能,可为 Iceberg 表提供部分只读接口。Iceberg 表引擎 已可用,但可能存在一些限制。ClickHouse 最初并不是为支持 schema 会被外部更改的表而设计的,这可能会影响 Iceberg 表引擎 的功能。因此,一些适用于常规表的功能可能无法使用,或者无法正常工作,尤其是在使用旧版 analyzer 时。为获得最佳兼容性,建议使用 Iceberg 表函数;同时,我们也会继续改进对 Iceberg 表引擎 的支持。
该引擎为 Amazon S3、Azure、HDFS 以及本地存储中的现有 Apache Iceberg 表提供只读集成。

创建表

请注意,Iceberg 表必须已存在于存储中;此命令不接受用于创建新表的 DDL 参数。
CREATE TABLE iceberg_table_s3
    ENGINE = IcebergS3(url,  [, NOSIGN | access_key_id, secret_access_key, [session_token]], format, [,compression], [,extra_credentials])

CREATE TABLE iceberg_table_azure
    ENGINE = IcebergAzure(connection_string|storage_account_url, container_name, blobpath, [account_name, account_key, format, compression])

CREATE TABLE iceberg_table_hdfs
    ENGINE = IcebergHDFS(path_to_table, [,format] [,compression_method])

CREATE TABLE iceberg_table_local
    ENGINE = IcebergLocal(path_to_table, [,format] [,compression_method])

引擎参数

这些参数的说明分别与 S3AzureBlobStorageHDFSFile 表引擎中的参数说明一致。 format 表示 Iceberg 表中数据文件的格式。 对于 IcebergS3,可使用可选参数 extra_credentials 传递 role_arn,以便在 ClickHouse Cloud 中进行基于角色的访问。有关配置步骤,请参阅 安全访问 S3 可以使用 命名集合 指定引擎参数。

示例

CREATE TABLE iceberg_table ENGINE=IcebergS3('http://test.s3.amazonaws.com/clickhouse-bucket/test_table', 'test', 'test')
使用命名集合:
<clickhouse>
    <named_collections>
        <iceberg_conf>
            <url>http://test.s3.amazonaws.com/clickhouse-bucket/</url>
            <access_key_id>test</access_key_id>
            <secret_access_key>test</secret_access_key>
        </iceberg_conf>
    </named_collections>
</clickhouse>
CREATE TABLE iceberg_table ENGINE=IcebergS3(iceberg_conf, filename = 'test_table')

别名

现在,表引擎 IcebergIcebergS3 的别名。

数据类型

下表展示了在进行 schema 推断时 (用于读取) Iceberg 数据类型与 ClickHouse 数据类型之间的映射关系。

基本类型

Iceberg 类型ClickHouse 类型说明
booleanBool
intInt32
long, bigintInt64
floatFloat32
doubleFloat64
dateDate32
timeInt64自午夜起的微秒数
timestampDateTime64(6)微秒,无时区
timestamptzDateTime64(6, 'UTC')微秒,UTC 时区
timestamp_nsDateTime64(9)纳秒,无时区 (仅适用于 Iceberg v3 及以上版本)
timestamptz_nsDateTime64(9, 'UTC')纳秒,UTC 时区 (仅适用于 Iceberg v3 及以上版本)
string, binaryString
uuidUUID
fixed(N)FixedString(N)
decimal(P, S)Decimal(P, S)

复合类型

Iceberg 类型ClickHouse 类型
listArray
mapMap
structTuple

Schema 演进

ClickHouse 支持读取 schema 会随时间演进的 Iceberg 表。这包括列被添加、删除或重新排序的表,以及列从必填变为可为空的表。此外,还支持以下类型转换:
  • int -> long
  • float -> double
  • decimal(P, S) -> decimal(P’, S) where P’ > P.
目前,尚不支持更改嵌套结构,也不支持更改数组和 Map 中元素的类型。 要读取使用动态 schema 推断创建后 schema 又发生变化的表,请在创建表时设置 allow_dynamic_metadata_for_data_lakes = true。

分区裁剪

ClickHouse 支持在针对 Iceberg 表的 SELECT 查询中进行分区裁剪,这有助于通过跳过无关的数据文件来优化查询性能。要启用分区裁剪,请设置 use_iceberg_partition_pruning = 1。有关 Iceberg 分区裁剪的更多信息,请参阅 https://iceberg.apache.org/spec/#partitioning

时间旅行

ClickHouse 支持 Iceberg 表的时间旅行,让你能够通过特定的时间戳或快照 ID 查询历史数据。

包含已删除行的表的处理

ClickHouse 支持读取使用以下删除方法的 Iceberg 表: 以下删除方法不支持

基本用法

 SELECT * FROM example_table ORDER BY 1 
 SETTINGS iceberg_timestamp_ms = 1714636800000
 SELECT * FROM example_table ORDER BY 1 
 SETTINGS iceberg_snapshot_id = 3547395809148285433
注意:在同一个查询中,不能同时指定 iceberg_timestamp_msiceberg_snapshot_id 参数。

重要注意事项

  • 快照通常会在以下情况下创建:
    • 向表中写入新数据时
    • 执行某种数据合并整理时
  • schema 变更通常不会创建快照——这会在对经历过 schema 演进的表使用时间旅行时带来一些重要特性。

示例场景

所有场景均使用 Spark 编写,因为 CH 目前尚不支持写入 Iceberg 表。

场景 1:没有新快照的 schema 变更

考虑以下操作顺序:
 -- 创建一个包含两列的表
  CREATE TABLE IF NOT EXISTS spark_catalog.db.time_travel_example (
  order_number int, 
  product_code string
  ) 
  USING iceberg 
  OPTIONS ('format-version'='2')

-- 向表中插入数据
  INSERT INTO spark_catalog.db.time_travel_example VALUES 
    (1, 'Mars')

  ts1 = now() // 一段伪代码

-- 修改表以添加新列
  ALTER TABLE spark_catalog.db.time_travel_example ADD COLUMN (price double)
 
  ts2 = now()

-- 向表中插入数据
  INSERT INTO spark_catalog.db.time_travel_example VALUES (2, 'Venus', 100)

   ts3 = now()

-- 查询各时间戳对应的表数据
  SELECT * FROM spark_catalog.db.time_travel_example TIMESTAMP AS OF ts1;

+------------+------------+
|order_number|product_code|
+------------+------------+
|           1|        Mars|
+------------+------------+
  SELECT * FROM spark_catalog.db.time_travel_example TIMESTAMP AS OF ts2;

+------------+------------+
|order_number|product_code|
+------------+------------+
|           1|        Mars|
+------------+------------+

  SELECT * FROM spark_catalog.db.time_travel_example TIMESTAMP AS OF ts3;

+------------+------------+-----+
|order_number|product_code|price|
+------------+------------+-----+
|           1|        Mars| NULL|
|           2|       Venus|100.0|
+------------+------------+-----+
不同时间戳下的查询结果:
  • 在 ts1 和 ts2 时:仅显示最初的两列
  • 在 ts3 时:显示全部三列,其中第一行的 price 为 NULL

场景 2:历史 schema 与当前 schema 的差异

在当前时刻执行时间旅行查询时,显示出的 schema 可能与当前表的 schema 不同:
-- 创建表
  CREATE TABLE IF NOT EXISTS spark_catalog.db.time_travel_example_2 (
  order_number int, 
  product_code string
  ) 
  USING iceberg 
  OPTIONS ('format-version'='2')

-- 向表中插入初始数据
  INSERT INTO spark_catalog.db.time_travel_example_2 VALUES (2, 'Venus');

-- 修改表以添加新列
  ALTER TABLE spark_catalog.db.time_travel_example_2 ADD COLUMN (price double);

  ts = now();

-- 使用时间戳语法查询当前时刻的表数据

  SELECT * FROM spark_catalog.db.time_travel_example_2 TIMESTAMP AS OF ts;

    +------------+------------+
    |order_number|product_code|
    +------------+------------+
    |           2|       Venus|
    +------------+------------+

-- 查询当前时刻的表数据
  SELECT * FROM spark_catalog.db.time_travel_example_2;
    +------------+------------+-----+
    |order_number|product_code|price|
    +------------+------------+-----+
    |           2|       Venus| NULL|
    +------------+------------+-----+
之所以会出现这种情况,是因为 ALTER TABLE 不会创建新的 快照;对于当前表,Spark 获取 schema_id 的值时,取自最新的元数据文件,而不是某个 快照。

场景 3:历史 schema 与当前 schema 的差异

第二点是,进行时间旅行时,你无法获取该表在尚未写入任何数据之前的状态:
-- 创建一张表
  CREATE TABLE IF NOT EXISTS spark_catalog.db.time_travel_example_3 (
  order_number int, 
  product_code string
  ) 
  USING iceberg 
  OPTIONS ('format-version'='2');

  ts = now();

-- 查询表在特定时间戳时的状态
  SELECT * FROM spark_catalog.db.time_travel_example_3 TIMESTAMP AS OF ts; -- 报错:Cannot find a 快照 older than ts.
在 ClickHouse 中,其行为与 Spark 保持一致。你可以直接把 Spark 的 Select 查询理解为 ClickHouse 的 Select 查询,两者的工作方式是一样的。

元数据文件解析

在 ClickHouse 中使用 Iceberg 表引擎时,系统需要找到描述 Iceberg 表结构的正确 metadata.json 文件。下面介绍这一解析过程的工作原理:
  1. 直接指定路径
  • 如果设置了 iceberg_metadata_file_path,系统会将其与 Iceberg 表目录路径拼接,使用这个精确路径。
  • 提供此设置后,其他所有解析设置都会被忽略。
  1. 表 UUID 匹配
  • 如果指定了 iceberg_metadata_table_uuid,系统将:
    • 只检查 metadata 目录中的 .metadata.json 文件
    • 筛选出包含 table-uuid 字段且与指定 UUID 匹配 (不区分大小写) 的文件
  1. 默认搜索
  • 如果上述两个设置都未提供,则 metadata 目录中的所有 .metadata.json 文件都会作为候选文件

选择最新的文件

根据上述规则识别出候选文件后,系统会进一步确定其中哪个文件最新:
  • 如果启用了 iceberg_recent_metadata_file_by_last_updated_ms_field
    • 选择 last-updated-ms 值最大的文件
  • 否则:
    • 选择版本号最大的文件
    • (在格式为 V.metadata.jsonV-uuid.metadata.json 的文件名中,版本号显示为 V)
注意:上述提到的所有设置 (除非另有明确说明) 均为引擎级设置,必须在创建表时按如下所示指定:
CREATE TABLE example_table ENGINE = Iceberg(
    's3://bucket/path/to/iceberg_table'
) SETTINGS iceberg_metadata_table_uuid = '6f6f6407-c6a5-465f-a808-ea8900e35a38';
注意:虽然 Iceberg 目录通常负责元数据解析,但 ClickHouse 的 Iceberg 表引擎会直接将存储在 S3 中的文件识别为 Iceberg 表,因此理解这些解析规则非常重要。

数据缓存

Iceberg 表引擎和表函数支持数据缓存,与 S3AzureBlobStorageHDFS 存储相同。请参见此处

元数据缓存

Iceberg 表引擎和表函数支持元数据缓存,可缓存 manifest 文件、manifest 列表和 metadata json 的相关信息。缓存存储在内存中。此功能由设置 use_iceberg_metadata_files_cache 控制,默认启用。

异步元数据预取

在创建 Iceberg 表时,可通过设置 iceberg_metadata_async_prefetch_period_ms 启用异步元数据预取。如果将其设为 0 (默认值) ,或未启用元数据缓存,则会禁用异步预取。 要启用此功能,需要指定一个非零的毫秒值,表示两次预取周期之间的时间间隔。 启用后,服务器会在后台周期性执行一项操作,列出远程 目录 并检测新的元数据版本。随后会对其进行解析,并递归遍历快照,拉取活动的 manifest 列表和 manifest 文件。 已存在于元数据缓存中的文件不会被重复下载。每个预取周期结束时,最新的元数据快照都会存放在元数据缓存中。
CREATE TABLE example_table ENGINE = Iceberg(
    's3://bucket/path/to/iceberg_table'
) SETTINGS
    iceberg_metadata_async_prefetch_period_ms = 60000;
为了在读取操作中尽可能利用异步元数据预取,应将 iceberg_metadata_staleness_ms 指定为查询参数或会话参数。默认情况下 (0,即未指定) ,在每个查询的上下文中,服务器都会从远程 目录 拉取最新元数据。 通过指定可容忍的元数据过期程度,服务器便可在不调用远程 目录 的情况下使用已缓存的元数据 快照 版本。如果缓存中存在元数据版本,且其下载时间处于给定的过期窗口内,则会使用该版本来处理查询。 否则,将从远程 目录 拉取最新版本。
SELECT count() FROM icebench_table WHERE ...
SETTINGS iceberg_metadata_staleness_ms=120000
注意:异步元数据预取在 ICEBERG_SCEDULE_POOL 中运行,这是一个服务器端线程池,用于对活动 Iceberg 表执行后台操作。该线程池的大小由服务器配置参数 iceberg_background_schedule_pool_size 控制 (默认值为 10) 。 注意:目前预期是,如果启用了异步预取,元数据缓存的大小应足以完整容纳所有活动表的最新元数据快照。

另请参见

最后修改于 2026年6月10日