为存储在 Amazon S3、Azure、HDFS 或本地的 Apache Iceberg 表提供表式接口。
语法
icebergS3(url [, NOSIGN | access_key_id, secret_access_key, [session_token]] [,format] [,compression_method] [,extra_credentials])
icebergS3(named_collection[, option=value [,..]])
icebergAzure(connection_string|storage_account_url, container_name, blobpath, [,account_name], [,account_key] [,format] [,compression_method])
icebergAzure(named_collection[, option=value [,..]])
icebergHDFS(path_to_table, [,format] [,compression_method])
icebergHDFS(named_collection[, option=value [,..]])
icebergLocal(path_to_table, [,format] [,compression_method])
icebergLocal(named_collection[, option=value [,..]])参数
这些参数的说明分别与表函数 s3、azureBlobStorage、HDFS 和 file 中对应参数的说明一致。
format 表示 Iceberg 表中数据文件的格式。
对于 icebergS3,可使用可选参数 extra_credentials 传入 role_arn,以便在 ClickHouse Cloud 中实现基于角色的访问。有关配置步骤,请参见 安全访问 S3。
返回值
一个具有指定结构的表,用于读取指定 Iceberg 表中的数据。
示例
SELECT * FROM icebergS3('http://test.s3.amazonaws.com/clickhouse-bucket/test_table', 'test', 'test')定义命名集合
下面是一个用于存储 URL 和凭据的命名集合配置示例:
<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>
<format>auto</format>
<structure>auto</structure>
</iceberg_conf>
</named_collections>
</clickhouse>SELECT * FROM icebergS3(iceberg_conf, filename = 'test_table')
DESCRIBE icebergS3(iceberg_conf, filename = 'test_table')使用数据目录
Iceberg 表也可以配合多种数据目录使用,例如 REST Catalog、AWS Glue Data Catalog 和 Unity Catalog。
要使用这些目录,请使用 IcebergS3 引擎创建表,并提供必要的设置。
例如,结合 MinIO 存储使用 REST Catalog:
CREATE TABLE `database_name.table_name`
ENGINE = IcebergS3(
'http://minio:9000/warehouse-rest/table_name/',
'minio_access_key',
'minio_secret_key'
)或者,使用 AWS Glue Data Catalog 和 S3:
CREATE TABLE `my_database.my_table`
ENGINE = IcebergS3(
's3://my-data-bucket/warehouse/my_database/my_table/',
'aws_access_key',
'aws_secret_key'
)Schema 演进
目前,借助 CH,你可以读取 schema 会随时间演变的 Iceberg 表。当前支持读取以下这类表:列被添加或删除,以及列顺序发生变化。你还可以将某一列从必须有值改为允许 NULL。此外,我们还支持简单类型之间允许的类型转换,即:
- int -> long
- float -> double
- decimal(P, S) -> decimal(P', S) where P' > P.
目前,尚不支持更改嵌套结构,或更改数组和 Map 中元素的类型。
分区裁剪
ClickHouse 支持在对 Iceberg 表执行 SELECT 查询时进行分区裁剪,这有助于通过跳过不相关的数据文件来优化查询性能。要启用分区裁剪,请设置 use_iceberg_partition_pruning = 1。有关 Iceberg 分区裁剪的更多信息,请参见 https://iceberg.apache.org/spec/#partitioning
时间旅行
ClickHouse 支持 Iceberg 表的时间旅行功能,让您可以使用特定的时间戳或快照 ID 查询历史数据。
处理含已删除行的表
ClickHouse 支持带有位置删除和等值删除的 Iceberg 表。自 v25.8 起支持等值删除。
以下删除方法暂不支持:
- 删除向量 (在 v3 中引入)
基本用法
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_ms 和 iceberg_snapshot_id 参数。
重要注意事项
-
快照通常会在以下情况下创建:
-
向表中写入新数据时
-
执行某种数据合并整理时
-
schema 变更通常不会创建快照——这会在对经历过 schema 演进的表使用时间旅行时带来一些重要行为。
示例场景
这些场景使用 Spark 来演示外部 Iceberg 写入器进行的 schema 变更。
场景 1:无新增快照时的 schema 变更
考虑以下操作顺序:
-- 创建一个包含两列的表
CREATE TABLE IF NOT EXISTS spark_catalog.db.time_travel_example (
order_number bigint,
product_code string
)
USING iceberg
OPTIONS ('format-version'='2')
- - 向不同时间戳下的查询结果:
- 在 ts1 和 ts2:仅显示最初的两列
- 在 ts3:显示全部三列,其中第一行的 price 为 NULL
场景 2:历史 schema 与当前 schema 的差异
在当前时刻执行时间旅行查询时,显示的 schema 可能与当前表的 schema 不同:
-- 创建表
CREATE TABLE IF NOT EXISTS spark_catalog.db.time_travel_example_2 (
order_number bigint,
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 a table
CREATE TABLE IF NOT EXISTS spark_catalog.db.time_travel_example_3 (
order_number bigint,
product_code string
)
USING iceberg
OPTIONS ('format-version'='2');
ts = now();
-- Query the table at a specific timestamp
SELECT * FROM spark_catalog.db.time_travel_example_3 TIMESTAMP AS OF ts; -- Finises with error: Cannot find a snapshot older than ts.在 ClickHouse 中,这种行为与 Spark 保持一致。你可以直接将 Spark 的 Select 查询理解为 ClickHouse 的 Select 查询,两者的工作方式相同。
元数据文件定位
在 ClickHouse 中使用 iceberg 表函数时,系统需要找到正确的 metadata.json 文件,该文件用于描述 Iceberg 表的结构。下面介绍这一定位过程的工作方式:
候选项搜索 (按优先顺序)
- 直接指定路径:
*如果设置了
iceberg_metadata_file_path,系统会将其与 Iceberg 表目录路径拼接,使用该精确路径。
- 提供此设置后,所有其他解析设置都会被忽略。
-
表 UUID 匹配: *如果指定了
iceberg_metadata_table_uuid,系统将: *仅查看metadata目录中的.metadata.json文件 *筛选出包含table-uuid字段且与指定 UUID 匹配的文件 (不区分大小写) -
默认搜索: *如果以上两个设置都未提供,
metadata目录中的所有.metadata.json文件都会成为候选项
选择最新文件
根据上述规则识别出候选文件后,系统会进一步确定其中哪个是最新的:
-
如果启用了
iceberg_recent_metadata_file_by_last_updated_ms_field: -
选择
last-updated-ms值最大的文件 -
否则:
-
选择版本号最高的文件
-
(对于格式为
V.metadata.json或V-uuid.metadata.json的文件名,版本号显示为V)
注意:上述所有设置都是表函数设置 (不是全局设置,也不是查询级设置) ,必须按如下所示指定:
SELECT * FROM iceberg('s3://bucket/path/to/iceberg_table',
SETTINGS iceberg_metadata_table_uuid = 'a90eed4c-f74b-4e5b-b630-096fb9d09021');注意:虽然 Iceberg 目录通常负责处理元数据解析,但 ClickHouse 中的 iceberg 表函数 会直接将存储在 S3 中的文件识别为 Iceberg 表,因此理解这些解析规则很重要。
元数据缓存
Iceberg 表引擎和表函数支持元数据缓存,可存储 manifest 文件、manifest 列表以及元数据 json 的相关信息。该缓存存储在内存中。此功能由设置 use_iceberg_metadata_files_cache 控制,且默认启用。
别名
表函数 iceberg 现为 icebergS3 的别名。
虚拟列
_path— 文件路径。类型:LowCardinality(String)。_file— 文件名。类型:LowCardinality(String)。_size— 文件大小 (单位:字节) 。类型:Nullable(UInt64)。如果文件大小未知,则值为NULL。_time— 文件的最后修改时间。类型:Nullable(DateTime)。如果时间未知,则值为NULL。_etag— 文件的 etag。类型:LowCardinality(String)。如果 etag 未知,则值为NULL。
写入 Iceberg 表
从 25.7 版本开始,ClickHouse 支持修改位于可写存储后端上的 Iceberg 表。
在修改或维护 Iceberg 表之前,请启用 allow_insert_into_iceberg 设置。如下所述,某些操作还需要额外设置:
SET allow_insert_into_iceberg = 1;创建表
要在可写 backend 上创建新的独立 Iceberg 表,请使用 Iceberg 表引擎,并显式指定 schema。 写入支持 Iceberg 规范中的所有数据格式,例如 Parquet、Avro、ORC。
示例
CREATE TABLE iceberg_writes_example
(
x Nullable(String),
y Nullable(Int32)
)
ENGINE = IcebergLocal('/home/scanhex12/iceberg_example/')注意:如需创建 version hint 文件,请启用 iceberg_use_version_hint 设置。
如果要压缩 metadata.json 文件,请在 iceberg_metadata_compression_method 设置中指定编解码器名称。
INSERT
创建新表后,您可以使用标准的 ClickHouse 语法插入数据。
示例
INSERT INTO iceberg_writes_example VALUES ('Pavel', 777), ('Ivanov', 993);
SELECT *
FROM iceberg_writes_example
FORMAT VERTICAL;
Row 1:
─DELETE
ClickHouse 也支持在 merge-on-read 格式下删除多余的行。 该查询会创建一个包含位置删除文件的新快照。
示例
ALTER TABLE iceberg_writes_example DELETE WHERE x != 'Ivanov';
SELECT *
FROM iceberg_writes_example
FORMAT VERTICAL;
Row 1:
─Schema 演进
ClickHouse 允许你对简单类型 (不包括 Tuple、Array 和 Map) 的列进行添加、删除、修改或重命名。
示例
ALTER TABLE iceberg_writes_example MODIFY COLUMN y Nullable(Int64);
SHOW CREATE TABLE iceberg_writes_example;
┌合并整理
ClickHouse 支持对 Iceberg 表执行合并整理。目前,它可以在更新元数据的同时,将位置删除文件合并到数据文件中。此前的快照 ID 和时间戳保持不变,因此仍可使用相同的值进行时间旅行查询。
使用方法:
SET allow_experimental_iceberg_compaction = 1
OPTIMIZE TABLE iceberg_writes_example;
SELECT *
FROM iceberg_writes_example
FORMAT VERTICAL;
Row 1:
─过期快照
Iceberg 表会在每次 INSERT、DELETE 或 UPDATE 操作时累积快照。随着时间推移,这可能会产生大量快照及其关联的数据文件。expire_snapshots 命令会删除旧快照,并清理不再被任何保留快照引用的数据文件。
语法:
ALTER TABLE iceberg_table EXECUTE expire_snapshots(
['timestamp']
[, expire_before = 'timestamp']
[, retention_period = '3d']
[, retain_last = 100]
[, snapshot_ids = [1, 2, 3, 4]]
[, dry_run = 1]
);默认情况下,要保留哪些快照由保留策略决定 (表属性 min-snapshots-to-keep、max-snapshot-age-ms 以及针对各 ref 的覆盖配置) 。指定 snapshot_ids 时,会绕过保留策略,并且只会将列出的快照纳入过期处理范围。
参数:
'timestamp'(位置参数) 或expire_before = 'timestamp'— 一个日期时间字符串 (例如'2024-06-01 00:00:00') ,按服务器时区解释。它相当于一个安全阀:timestamp-ms等于或晚于该值的快照会受到保护,不会被过期处理,即使按保留策略原本应被过期处理。可与snapshot_ids组合使用;在这种情况下,列表中等于或晚于该时间戳的快照不会被过期处理。retention_period = '<duration>'— 仅对本次调用覆盖表级history.expire.max-snapshot-age-ms。早于该时长的快照 (从当前时刻往前计算) 会成为过期候选。该值是一个时长字符串,由一个或多个连续拼接的{number}{unit}对组成。支持的单位:y(365 天) 、w(7 天) 、d(24 小时) 、h(60 分钟) 、m(60 秒) 、s(1 秒) 、ms(1 毫秒) 。单位可以组合,例如'3d'、'12h'、'1d12h30m'、'500ms'。retain_last = N— 仅对本次调用覆盖表级history.expire.min-snapshots-to-keep。无论快照多旧,始终至少保留N个快照。snapshot_ids = [id1, id2, ...]— 仅对列出的快照 ID 执行过期处理 (当前快照、分支或标签引用的快照除外) 。此模式会完全绕过保留策略,且不能与retention_period或retain_last组合使用。dry_run = 1— 计算哪些内容会被过期处理,并返回指标,但不会写入新元数据或删除文件。
示例:
SET allow_insert_into_iceberg = 1;
-- 通过插入数据创建一些快照
INSERT INTO iceberg_table VALUES (1);
INSERT INTO iceberg_table VALUES (2);
INSERT INTO iceberg_table VALUES (3);
-- 仅使用保留策略使快照过期
ALTER TABLE iceberg_table EXECUTE expire_snapshots();
-- 使用安全熔断:保护晚于该时间戳的快照(位置参数语法)
ALTER TABLE iceberg_table EXECUTE expire_snapshots('2025-01-01 00:00:00');
-- 使用命名参数形式实现相同效果
ALTER TABLE iceberg_table EXECUTE expire_snapshots(expire_before = '2025-01-01 00:00:00');
-- 仅针对本次执行覆盖保留参数
ALTER TABLE iceberg_table EXECUTE expire_snapshots(retention_period = '3d', retain_last = 10);
-- 使指定快照过期
ALTER TABLE iceberg_table EXECUTE expire_snapshots(snapshot_ids = [101, 102, 103]);
-- 试运行预览(不更新元数据,不删除文件)
ALTER TABLE iceberg_table EXECUTE expire_snapshots(retention_period = '1d', dry_run = 1);输出:
该命令会返回一个包含两列 (metric_name String、metric_value Int64) 的表,其中每个指标对应一行。指标名称遵循 Iceberg 规范:
| metric_name | 描述 |
|---|---|
deleted_data_files_count |
已删除的数据文件数量 |
deleted_position_delete_files_count |
已删除的位置删除文件数量 |
deleted_equality_delete_files_count |
已删除的等值删除文件数量 |
deleted_manifest_files_count |
已删除的 manifest 文件数量 |
deleted_manifest_lists_count |
已删除的 manifest 列表文件数量 |
deleted_statistics_files_count |
已删除的统计信息文件数量 (当前始终为 0) |
dry_run |
1 表示试运行模式,0 表示正常执行 |
该命令执行以下步骤:
- 评估保留策略 (见下文) ,以确定必须保留哪些快照
- 如果提供了时间戳参数,还会额外保护该时间戳及之后的所有快照
- 让既未被保留策略保留、也未受时间戳保护机制保护的快照过期
- 计算哪些文件仅与已过期的快照关联
- 在正常模式下:生成不包含已过期快照的新元数据
- 在正常模式下:物理删除不可达的 manifest 列表、manifest 文件和数据文件
- 在
dry_run = 1模式下:跳过步骤 5 和 6,仅返回计算出的指标
快照保留策略
expire_snapshots 命令遵循 Iceberg 快照保留策略。保留策略通过 Iceberg 表属性以及按引用的覆盖设置进行配置:
| 属性 | 范围 | 默认值 | 描述 |
|---|---|---|---|
history.expire.min-snapshots-to-keep |
表 | iceberg_expire_default_min_snapshots_to_keep (默认值为 1) |
每个分支的祖先链中至少要保留的快照数量 |
history.expire.max-snapshot-age-ms |
表 | iceberg_expire_default_max_snapshot_age_ms (默认值为 432000000,即 5 天) |
分支中要保留的快照的最大保留时长 (毫秒) |
history.expire.max-ref-age-ms |
表 | iceberg_expire_default_max_ref_age_ms (默认值为 ∞) |
快照引用 (分支或标签) 在被移除前允许的最大保留时长 (毫秒) |
每个快照引用 (Iceberg 元数据中的 refs) 都可以通过引用级字段覆盖这些值:min-snapshots-to-keep、max-snapshot-age-ms 和 max-ref-age-ms。
保留规则评估:
- 对于每个分支 (包括
main) :从分支头开始遍历其祖先链。只要满足以下任一条件,就会保留该快照:- 该快照位于祖先链中的前
min-snapshots-to-keep个快照之内 - 该快照的存在时长未超过
max-snapshot-age-ms(即now - timestamp-ms <= max-snapshot-age-ms)
- 该快照位于祖先链中的前
- 对于标签:被标记的快照会被保留,除非该标签已超过其
max-ref-age-ms,此时会移除该标签引用 - 非
main引用 如果其存在时长超过max-ref-age-ms,将被完全移除 (main分支永远不会被移除) - 悬空引用 如果指向不存在的快照,将在发出警告后被移除
- 当前快照始终会被保留,无论保留设置如何
所需权限:
需要 ALTER TABLE EXECUTE 权限,它在 ClickHouse 访问控制层级中是 ALTER TABLE 的子权限。你可以单独授予该权限,也可以通过其父权限授予:
-- 仅授予 EXECUTE 权限
GRANT ALTER TABLE EXECUTE ON my_iceberg_table TO my_user;
-- 或授予所有 ALTER TABLE 权限(包含 ALTER TABLE EXECUTE)
GRANT ALTER TABLE ON my_iceberg_table TO my_user;删除孤立文件
孤立文件是指存储中存在、但未被 Iceberg 表元数据中的任何快照引用的文件。它们会因写入失败、合并整理后的清理不完整以及操作中断而不断累积,导致存储空间持续增长且无上限。remove_orphan_files 命令可用于识别并删除这些孤立文件。
语法:
-- 位置形式:单个未命名的 older_than 参数
ALTER TABLE iceberg_table EXECUTE remove_orphan_files('timestamp')
-- 命名形式
ALTER TABLE iceberg_table EXECUTE remove_orphan_files(
older_than = 'timestamp',
location = 'path',
dry_run = 0|1
)
-- 无参数:使用所有默认值(older_than = 3 天前)
ALTER TABLE iceberg_table EXECUTE remove_orphan_files()参数:
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
older_than |
String (时间戳) |
3 天前 (可通过 iceberg_orphan_files_older_than_seconds 配置) |
仅将最后修改时间早于此时间戳的文件视为孤立文件候选文件。作为安全保护,防止删除正在进行的写入所产生的文件。 |
location |
String |
表位置 | 将扫描范围限制为表位置下的特定子目录 (例如 'data/' 或 'metadata/') 。 |
dry_run |
UInt64 |
0 |
当为 1 时,会识别孤立文件并返回结果摘要,但不会实际删除任何内容。 |
示例:
-- 删除早于特定时间戳的孤立文件
ALTER TABLE iceberg_table EXECUTE remove_orphan_files('2026-03-01 00:00:00');
-- 试运行:预览将被删除的文件
ALTER TABLE iceberg_table EXECUTE remove_orphan_files(dry_run = 1);
-- 仅扫描数据目录
ALTER TABLE iceberg_table EXECUTE remove_orphan_files(
older_than = '2026-03-01 00:00:00',
location = 'data/'
);
-- 将位置参数 older_than 与命名参数结合使用
ALTER TABLE iceberg_table EXECUTE remove_orphan_files(
'2026-03-01 00:00:00',
dry_run = 1
);输出:
该命令会返回一个表,包含 metric_name 和 metric_value 两列,按类别显示已删除文件的数量 (或在 dry_run 模式下将要删除的文件数量) 。文件类别会基于文件命名约定,采用尽力而为的启发式规则进行分类;不匹配任何特定模式的文件默认计入 deleted_data_files_count:
| metric_name | metric_value |
|---|---|
| deleted_data_files_count | 5 |
| deleted_position_delete_files_count | 2 |
| deleted_equality_delete_files_count | 0 |
| deleted_manifest_files_count | 3 |
| deleted_manifest_lists_count | 1 |
| deleted_metadata_files_count | 0 |
| deleted_statistics_files_count | 0 |
| skipped_missing_metadata_count | 0 |
| failed_deletions_count | 0 |
设置:
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
allow_iceberg_remove_orphan_files |
Bool |
false |
用于启用该功能的门控设置 (Experimental) 。 |
iceberg_orphan_files_older_than_seconds |
UInt64 |
259200 (3 days) |
省略该参数时,older_than 的默认阈值 (单位为秒) 。 |