引言
2026年5月20日,DuckDB 发布了 v1.5.3 补丁版本,同时推出了 DuckCon #7 阿姆斯特丹大会。虽然 v1.5.3 核心版本的更新主要集中在 bug 修复上,但伴随发布的 DuckDB-Iceberg 扩展 带来了令人瞩目的新功能——这被官方称为"Iceberg 写入门系列博客的第二部分"。
在 v1.4 时代,DuckDB 对 Iceberg 的支持主要集中在读取能力上。从 v1.5.0 的 DuckLake v1.0 标准到 v1.5.3,DuckDB-Iceberg 已经从一个"读扩展"蜕变为功能完备的湖格式写入引擎。
本文将深入剖析 v1.5.3 中 DuckDB-Iceberg 的所有核心新特性,并通过完整的 SQL 示例帮助你快速上手。
一、DuckDB-Iceberg 快速入门
在使用新特性之前,首先需要连接到你的 Iceberg REST Catalog。DuckDB-Iceberg 支持多种目录后端:
- Apache Polaris — Apache 基金会推荐的统一目录服务
- Lakekeeper — 高性能云原生 Iceberg 目录
- Amazon S3 Tables — AWS 原生的 S3 表格支持
连接命令如下:
ATTACH 'warehouse_name' AS my_datalake (
TYPE iceberg,
other options
);
连接成功后,你就可以使用标准的 SQL 语法操作 Iceberg 表了,无需学习新的 API。
二、MERGE INTO 完整支持 —— 湖格式 UPSERT 的终极方案
DuckDB 的 MERGE INTO 语句是处理 UPSERT(插入或更新)操作的推荐方式。对于 Iceberg 这类没有主键约束的湖格式表,MERGE INTO 尤为重要。
2.1 基本用法
-- 创建目标表
CREATE TABLE my_datalake.default.people (
id INTEGER,
name VARCHAR,
salary FLOAT
);
INSERT INTO my_datalake.default.people
VALUES (1, 'John', 92_000.0), (2, 'Anna', 100_000.0);
结果:
┌───────┬─────────┬──────────┐
│ id │ name │ salary │
│ int32 │ varchar │ float │
├───────┼─────────┼──────────┤
│ 1 │ John │ 92000.0 │
│ 2 │ Anna │ 100000.0 │
└───────┴─────────┴──────────┘
2.2 执行 UPSERT 操作
MERGE INTO my_datalake.default.people AS target
USING (
FROM (VALUES
(1, 'John', 105_000.0),
(3, 'Sarah', 95_000.0)
) t(id, name, salary)
) AS upserts
ON (upserts.id = target.id)
WHEN MATCHED THEN UPDATE
WHEN NOT MATCHED THEN INSERT;
查询结果:
┌───────┬─────────┬──────────┐
│ id │ name │ salary │
│ int32 │ varchar │ float │
├───────┼─────────┼──────────┤
│ 1 │ John │ 105000.0 │
│ 2 │ Anna │ 100000.0 │
│ 3 │ Sarah │ 95000.0 │
└───────┴─────────┴──────────┘
2.3 合并删除操作
MERGE INTO 还可以在同一条语句中包含删除逻辑:
MERGE INTO my_datalake.default.people AS target
USING (VALUES (1, NULL, NULL)) AS src(id, name, salary)
ON (src.id = target.id)
WHEN MATCHED THEN DELETE;
底层实现上,MERGE INTO 使用 Merge-on-Read(MoR) 语义,对于 UPDATE 和 DELETE 操作会写入位置删除文件(positional deletes),不会重写整个数据文件。
三、ALTER TABLE —— 告别静态表架构
在 v1.4 中,Iceberg 表的架构演进是一个文档化但尚未实现的限制。v1.5.3 填补了这一空白,支持了最常见的架构操作。
3.1 完整操作示例
-- 创建表
CREATE TABLE my_datalake.default.simple_table AS
FROM (VALUES
(1, 'Andy'),
(2, 'Bob'),
(3, 'Claire'),
(4, 'Mr. Duck')) t(col1, col2);
-- 重命名表
ALTER TABLE my_datalake.default.simple_table
RENAME TO renamed_table;
-- 添加列
ALTER TABLE my_datalake.default.renamed_table
ADD COLUMN col3 DOUBLE;
-- 重命名列
ALTER TABLE my_datalake.default.renamed_table
RENAME COLUMN col2 TO name;
-- 删除列
ALTER TABLE my_datalake.default.renamed_table
DROP COLUMN col3;
-- 设置格式版本
ALTER TABLE my_datalake.default.renamed_table
SET ('format-version' = 3);
3.2 工作原理
每次 ALTER TABLE 操作都会更新 Iceberg 表的 current-schema-id。由于 Iceberg 的架构演进是纯元数据操作,不需要重写任何数据文件,因此速度极快,对查询性能无影响。
变更会立即对其他连接该目录的 Iceberg 感知引擎可见。
四、Partition Transforms —— bucket 与 truncate 支持
Iceberg 规范定义了多种分区变换(Partition Transforms),决定了数据文件在磁盘上的布局方式。v1.5.3 新增了对 bucket 和 truncate 变换的支持。
4.1 Bucket 变换
bucket(N, col) 将列的值哈希到 N 个桶中,适合高基数列的稳定分区:
CREATE TABLE my_datalake.default.events (
event_id BIGINT,
user_id BIGINT,
country VARCHAR,
payload VARCHAR
)
PARTITIONED BY (bucket(16, user_id), truncate(2, country));
INSERT INTO my_datalake.default.events
VALUES
(1, 1001, 'United States', 'click'),
(2, 1002, 'United Kingdom', 'view'),
(3, 1003, 'Germany', 'click'),
(4, 1004, 'Netherlands', 'view');
4.2 Truncate 变换
truncate(W, col) 根据前 W 个字符(或数值列向下舍入到 W 的倍数)分组,适合前缀分区场景。
4.3 验证分区效果
SELECT file_path, record_count
FROM iceberg_metadata(my_datalake.default.events)
WHERE content = 'EXISTING';
更新和删除操作对 bucket/truncate 分区表同样支持,底层同样使用位置删除文件。
五、Iceberg Schema Properties —— 命名空间级别元数据管理
Iceberg 目录允许在命名空间(Schema)级别附加任意键值对属性。这些属性通常用于记录所有权、描述信息、默认存储位置等。
5.1 核心函数
DuckDB-Iceberg v1.5.3 提供了三个专用函数:
| 函数 | 功能 |
|---|---|
iceberg_schema_properties(ns) | 读取命名空间属性 |
set_iceberg_schema_properties(ns, props) | 设置/更新属性 |
remove_iceberg_schema_properties(ns, keys) | 删除指定属性 |
5.2 使用示例
-- 设置命名空间属性
CALL set_iceberg_schema_properties(my_datalake.default, {
'owner': 'analytics-team',
'description': 'Default analytics schema'
});
-- 查看属性
SELECT * FROM iceberg_schema_properties(my_datalake.default);
┌─────────────┬──────────────────────────┐
│ key │ value │
│ varchar │ varchar │
├─────────────┼──────────────────────────┤
│ owner │ analytics-team │
│ description │ Default analytics schema │
└─────────────┴──────────────────────────┘
-- 删除属性
CALL remove_iceberg_schema_properties(
my_datalake.default,
['description']
);
属性通过 Iceberg REST Catalog 写入,其他 Iceberg 感知引擎可以立即看到更新。
六、Iceberg V3 规范支持 —— 下一代湖格式
Iceberg v3 规范引入了多项重大改进,DuckDB-Iceberg v1.5.3 已全面支持读取和写入 V3 表。
6.1 V3 核心特性
| 特性 | 说明 |
|---|---|
VARIANT 类型 | 原生支持半结构化数据存储 |
TIMESTAMP_NS 类型 | 纳秒级时间戳精度 |
| 列级默认值 | Schema 级别定义列的默认值 |
| 二进制删除向量 | 使用 Puffin 格式替代 Parquet 删除文件 |
| 行血缘追踪 | 追踪数据行来源 |
6.2 二进制删除向量
这是 V3 最重要的改进之一。 在 V2 表中,DuckDB-Iceberg 将删除操作写为 Parquet 文件;在 V3 表中,同样的信息被编码为更紧凑的二进制删除向量(Puffin 文件)。
-- 创建 V3 表
CREATE TABLE my_datalake.default.v3_table
WITH ('format-version' = 3) AS
FROM (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 表自动写入二进制删除向量)
DELETE FROM my_datalake.default.v3_table
WHERE id = 1;
SELECT * FROM my_datalake.default.v3_table;
┌───────┬──────────────────┬───────────────────────────────┐
│ id │ payload │ event_time │
│ int32 │ variant │ timestamp_ns │
├───────┼──────────────────┼───────────────────────────────┤
│ 2 │ {"kind": "view"} │ 2026-05-20 12:00:00.987654321 │
└───────┴──────────────────┴───────────────────────────────┘
通过元数据查询可以看到删除以 Puffin 格式写入:
SELECT manifest_content, content, file_format
FROM iceberg_metadata(my_datalake.default.v3_table);
┌──────────────────┬──────────────────┬─────────────┐
│ manifest_content │ content │ file_format │
│ varchar │ varchar │ varchar │
├──────────────────┼──────────────────┼─────────────┤
│ DATA │ EXISTING │ parquet │
│ DELETE │ POSITION_DELETES │ puffin │
└──────────────────┴──────────────────┴─────────────┘
DuckDB 会根据表的 format-version 自动选择正确的写入格式。
七、与传统工具对比
| 特性 | DuckDB-Iceberg v1.5.3 | Apache Spark | Delta Lake (Spark) | AWS Glue |
|---|---|---|---|---|
| 部署复杂度 | ⭐ 无需集群 | ⭐⭐⭐⭐⭐ YARN/K8s | ⭐⭐⭐⭐⭐ Spark 集群 | ⭐⭐⭐⭐ 云管理 |
| MERGE INTO | ✅ 完整支持 | ✅ DataFrame API | ✅ 完整支持 | ⚠️ 有限 |
| ALTER TABLE | ✅ 架构演进 | ⚠️ 需重写 | ✅ 完整支持 | ⚠️ 有限 |
| 分区变换 | ✅ bucket/truncate | ✅ 所有变换 | ✅ 所有变换 | ⚠️ 有限 |
| V3 支持 | ✅ 读写 | ⚠️ 部分 | ⚠️ 部分 | ❌ |
| 二进制删除向量 | ✅ Puffin | ❌ | ❌ | ❌ |
| Schema Properties | ✅ 原生函数 | ⚠️ API | ⚠️ Delta 属性 | ❌ |
| 查询性能 | ⭐⭐⭐⭐⭐ 原生优化 | ⭐⭐ 启动开销 | ⭐⭐ 启动开销 | ⭐⭐ 云延迟 |
| 学习曲线 | ⭐ SQL 直写 | ⭐⭐⭐ DataFrame | ⭐⭐⭐ Spark SQL | ⭐⭐⭐ 控制台 |
八、变现建议
8.1 数据服务产品线
利用 DuckDB-Iceberg 的低运维成本特性,你可以构建以下高利润数据服务:
- 企业级数据湖托管服务 — 为客户部署和管理 Iceberg 数据湖,利用 DuckDB 的零运维特性将成本控制在 Spark 方案的 30% 以下
- 实时数据同步 SaaS — 基于 MERGE INTO 的 Upsert 能力,构建面向电商/金融的实时数据同步管道
- 数据湖迁移咨询 — 帮助企业从传统 Hive/Parquet 迁移到 Iceberg V3,利用 DuckDB 的兼容性实现无损迁移
8.2 技术栈组合建议
| 业务场景 | DuckDB + Iceberg + | 目标客户 |
|---|---|---|
| 实时数据湖 | Debezium + Kafka | 电商平台 |
| BI 分析层 | Metabase/Superset | 中小企业 |
| AI 数据管道 | DuckDB-Variant + Lance | AI 初创公司 |
| 数据治理 | Apache Polaris + Ranger | 金融机构 |
8.3 变现路线图
Phase 1 (0-3月): 搭建技术 Demo + 技术博客引流
└── 发布 DuckDB-Iceberg 系列教程,积累 SEO 流量
Phase 2 (3-6月): 推出标准化数据湖解决方案
└── 基于 DuckDB-Iceberg 部署方案的产品化
Phase 3 (6-12月): 构建数据湖管理平台 SaaS
└── 将 DuckDB-Iceberg 的管理操作封装为 Web 平台
结论
DuckDB v1.5.3 中的 DuckDB-Iceberg 扩展已经从一个"读取优先"的扩展,进化为功能全面的湖格式写入引擎。MERGE INTO、ALTER TABLE、bucket/truncate 分区变换、V3 规范支持以及 Schema Properties 等功能,填补了此前与 Spark/Delta 生态的关键差距。
随着 DuckLake v1.0 标准的正式发布和 Quack 客户端-服务器协议的推出,DuckDB 正在构建一个完整的自托管湖仓一体生态。对于追求低运维成本和高查询性能的团队来说,DuckDB-Iceberg 已经是一个成熟的选择。
⚠️ 注意:
GEOGRAPHY和UNKNOWN类型目前在 DuckDB-Iceberg 中尚未支持,计划在 DuckDB v2.0.0 中添加。
如果你希望在特定功能上获得优先级支持,可以在 DuckDB-Iceberg GitHub 仓库 提交 Issue,或直接联系 DuckLabs 的技术团队。
