来源:https://duckdb.org/2026/05/29/new-iceberg-features.html
DuckDB-Iceberg 在 v1.5.3 中的新特性
作者:Tom Ebergen, Thijs Bruineman
日期:2026-05-29
阅读时间:5 分钟
摘要:DuckDB-Iceberg 现在拥有许多新特性来支持 Iceberg 表和 Iceberg REST Catalog,包括:MERGE INTO、ALTER TABLE、分区转换(partition transforms)、V3 支持等!
尽管开发 DuckLake v1.0 和 Quack 所需的功能需要大量工作,DuckLabs 团队仍在努力开发 DuckDB-Iceberg 扩展。在这篇博文中,我们将演示 DuckDB v1.5.3 中可用的一些功能。其中许多功能在我们上一篇以 Iceberg 为主题的博文“DuckDB-Iceberg 中的写入操作”中被指定为未来版本发布的功能——你可以将本文视为那篇博文的“第 2 部分”。
入门指南
要体验新的 DuckDB-Iceberg 功能,你需要连接到常用的 Iceberg REST Catalog。有很多方法可以做到这一点:请查看“连接到 REST Catalog”页面,其中包含连接到 Apache Polaris 和 Lakekeeper 等 catalog 的说明。如果你想连接到 Amazon S3 Tables,请查阅“连接到 S3 Tables”页面。无论如何,你的ATTACH命令看起来会像这样:
ATTACH'warehouse_name'ASmy_datalake(TYPEiceberg,other options);MERGE INTO 支持
当目标表没有主键时(所有数据湖仓格式都是这种情况),DuckDB 的MERGE INTO语句是表达 upsert 操作的推荐方式。从 v1.5.3 开始,MERGE INTO现已完全支持 Iceberg 表。你可以在一条语句中对 Iceberg 表应用一个变更集,按行决定是插入、更新还是删除。
我们以这张表为例:
CREATETABLEmy_datalake.default.people(idINTEGER,nameVARCHAR,salaryFLOAT);INSERTINTOmy_datalake.default.peopleVALUES(1,'John',92_000.0),(2,'Anna',100_000.0);┌───────┬─────────┬──────────┐ │ id │ name │ salary │ │ int32 │varchar│float│ ├───────┼─────────┼──────────┤ │1│ John │92000.0│ │2│ Anna │100000.0│ └───────┴─────────┴──────────┘让我们对该表运行一次更新,包含两条记录,一条增加 ID 为 1 的人的薪水,另一条添加一个 ID 为 3 的新人。
MERGEINTOmy_datalake.default.peopleAStargetUSING(FROM(VALUES(1,'John',105_000.0),(3,'Sarah',95_000.0))t(id,name,salary))ASupsertsON(upserts.id=target.id)WHENMATCHEDTHENUPDATEWHENNOTMATCHEDTHENINSERT;查询结果时,我们得到以下结果:
SELECT*FROMmy_datalake.default.peopleORDERBYid;┌───────┬─────────┬──────────┐ │ id │ name │ salary │ │ int32 │varchar│float│ ├───────┼─────────┼──────────┤ │1│ John │105000.0│ │2│ Anna │100000.0│ │3│ Sarah │95000.0│ └───────┴─────────┴──────────┘你也可以将匹配(MATCHED)和不匹配(NOT MATCHED)分支与WHEN MATCHED THEN DELETE组合起来,在同一个语句中表达一个删除集。与UPDATE和DELETE一样,MERGE INTO使用读时合并(merge-on-read)语义,并将位置删除(positional deletes)写入 Iceberg 表。
ALTER TABLE 支持
在 DuckDB v1.4 的 Iceberg 扩展中,Iceberg 表缺乏模式演化(schema evolution)是一个已知的限制。在 v1.5.3 中,ALTER TABLE语句现已支持 Iceberg 表,涵盖了最常见的模式演化操作。
-- 创建表CREATETABLEmy_datalake.default.simple_tableASFROM(VALUES(1,'Andy'),(2,'Bob'),(3,'Claire'),(4,'Mr. Duck'))t(col1,col2);-- 重命名表ALTERTABLEmy_datalake.default.simple_tableRENAMETOrenamed_table;-- 添加列ALTERTABLEmy_datalake.default.renamed_tableADDCOLUMNcol3DOUBLE;-- 重命名列ALTERTABLEmy_datalake.default.renamed_tableRENAMECOLUMNcol2TOname;-- 删除列ALTERTABLEmy_datalake.default.renamed_tableDROPCOLUMNcol3;-- 设置格式版本ALTERTABLEmy_datalake.default.renamed_tableSET('format-version'=3);在模式更改后查询该表,我们得到以下结果:
SELECT*FROMmy_datalake.default.renamed_tableORDERBYcol1;┌───────┬──────────┐ │ col1 │ name │ │ int32 │varchar│ ├───────┼──────────┤ │1│ Andy │ │2│ Bob │ │3│ Claire │ │4│ Mr.Duck │ └───────┴──────────┘在后台,每个ALTER TABLE语句都会更新 Iceberg 表的current-schema-id。这些更改在其他能够识别 Iceberg 的引擎下次查询LoadTableInformation端点时就会变得可见。Iceberg 模式演化仅涉及元数据,因此不会重写任何数据文件。
truncate 和 bucket 支持
Iceberg 规范定义了多种分区转换(partition transforms),用于确定数据文件在磁盘上的布局方式。在 v1.5.3 中,DuckDB-Iceberg 支持创建、插入和更新使用bucket和truncate分区转换的表。
bucket(N, col)转换将列的值哈希到 N 个桶中,这在你想对高基数列进行稳定分区时非常有用。truncate(W, col)按前 W 个字符对行进行分组(对于数值列,则按向下舍入到 W 的倍数进行分组),这对于基于前缀的分区非常有用。
CREATETABLEmy_datalake.default.events(event_idBIGINT,user_idBIGINT,countryVARCHAR,payloadVARCHAR)PARTITIONEDBY(bucket(16,user_id),truncate(2,country));INSERTINTOmy_datalake.default.eventsVALUES(1,1001,'United States','click'),(2,1002,'United Kingdom','view'),(3,1003,'Germany','click'),(4,1004,'Netherlands','view');你可以检查生成的数据文件以验证分区:
SELECTfile_path,record_countFROMiceberg_metadata(my_datalake.default.events)WHEREcontent='EXISTING';针对使用bucket和truncate分区的表的更新和删除操作也得到支持,在读时合并语义下使用位置删除。
Iceberg 模式属性
Iceberg catalog 允许在模式(namespace)级别附加任意的键值属性。这些属性通常用于记录所有权、描述、默认存储位置或任何适用于模式中每个表的其他元数据。
iceberg_schema_propertiesset_iceberg_schema_propertiesremove_iceberg_schema_properties
你可以如下使用它们:
-- 设置模式属性CALLset_iceberg_schema_properties(my_datalake.default,{'owner':'analytics-team','description':'Default analytics schema'});-- 读取模式属性SELECT*FROMiceberg_schema_properties(my_datalake.default);┌─────────────┬──────────────────────────┐ │key│value│ │varchar│varchar│ ├─────────────┼──────────────────────────┤ │ owner │ analytics-team │ │ description │Defaultanalyticsschema│ └─────────────┴──────────────────────────┘-- 删除模式属性CALLremove_iceberg_schema_properties(my_datalake.default,['description']);模式属性通过 Iceberg REST Catalog 写入,因此任何连接到同一 catalog 的其他能够识别 Iceberg 的引擎都会立即看到更新。返回的值是剩余模式属性的数量。
V3 支持
Iceberg v3 规范引入了几个新特性,DuckDB-Iceberg 现在对这些特性同时支持读取和写入:
VARIANT和TIMESTAMP_NS数据类型- 列的模式级默认值
- 二进制删除向量
- 行血缘追踪
实际中最大的变化是二进制删除向量。在 v2 表中,DuckDB-Iceberg 将位置删除写入 Parquet 文件;在 v3 表中,相同的信息被编码为更紧凑的二进制删除向量(Puffin 文件)。DuckDB 会根据表的format-version自动选择正确的格式。
你可以通过在创建表时设置format-version表属性来创建一个 v3 表:
CREATETABLEmy_datalake.default.v3_tableWITH('format-version'=3)ASFROM(VALUES(1,{'kind':'click','x':10}::VARIANT,TIMESTAMP_NS'2026-05-20 12:00:00.123456789'),(2,{'kind':'view'}::VARIANT,TIMESTAMP_NS'2026-05-20 12:00:00.987654321'))t(id,payload,event_time);-- 针对 v3 表的删除操作会写入二进制删除向量DELETEFROMmy_datalake.default.v3_tableWHEREid=1;SELECT*FROMmy_datalake.default.v3_table;┌───────┬──────────────────┬───────────────────────────────┐ │ id │ payload │ event_time │ │ int32 │ variant │ timestamp_ns │ ├───────┼──────────────────┼───────────────────────────────┤ │2│ {"kind":"view"} │2026-05-2012:00:00.987654321│ └───────┴──────────────────┴───────────────────────────────┘查看表的元数据可以确认删除操作是作为删除向量而不是位置删除 Parquet 文件写入的:
SELECTmanifest_content,content,file_formatFROMiceberg_metadata(my_datalake.default.v3_table);┌──────────────────┬──────────────────┬─────────────┐ │ manifest_content │ content │ file_format │ │varchar│varchar│varchar│ ├──────────────────┼──────────────────┼─────────────┤ │DATA│ EXISTING │ parquet │ │DELETE│ POSITION_DELETES │ puffin │ └──────────────────┴──────────────────┴─────────────┘注意:DuckDB-Iceberg 目前还不支持 Geography 类型和 Unknown 类型;我们计划在 DuckDB v2.0.0 中添加它们。
结论与未来工作
通过这些功能,DuckDB-Iceberg 已经填补了上一篇博文中提到的许多空白:分区写入、模式演化、MERGE INTO以及许多 Iceberg v3 功能现已可用。未来还有更多功能将要实现,一如既往,如果您希望某个特定功能得到优先处理,请通过 DuckDB-Iceberg GitHub 仓库与我们联系,或直接与我们的工程师沟通。