Featured image of post DuckDB Iceberg v1.5.3 新特性全解析:MERGE INTO、ALTER TABLE 与 V3 支持

DuckDB Iceberg v1.5.3 新特性全解析:MERGE INTO、ALTER TABLE 与 V3 支持

深入解析 DuckDB v1.5.3 中 Iceberg 扩展的重大更新,包括 MERGE INTO 写入、ALTER TABLE 模式演化、bucket/truncate 分区变换、Iceberg V3 格式支持及 Schema Properties 管理功能。

引言

2026年5月29日,DuckDB 团队发布了 Iceberg 扩展的重大更新博客,带来了多项备受期待的功能。作为 DuckDB v1.5.3 的一部分,这些新特性大幅缩小了 DuckDB-Iceberg 与传统 Iceberg 引擎之间的功能差距,涵盖了写入操作、模式演化、高级分区策略以及最新的 Iceberg V3 格式支持。

在此之前,DuckDB 对 Iceberg 的支持主要集中在读取和数据写入的基础能力上。随着 v1.5.3 的发布,MERGE INTOALTER TABLEbucket/truncate 分区变换V3 格式等关键功能已全面可用。本文将逐一深入解析这些新特性,并通过可执行的 SQL 示例帮助读者快速上手。

对于正在构建数据湖仓(Lakehouse)架构的团队来说,这些更新意味着 DuckDB 可以更加无缝地融入现有的 Iceberg 生态,无论是作为查询引擎还是数据写入工具。

一、MERGE INTO:一键式 Upsert 操作

功能概述

MERGE INTO(也称为 Upsert)是数据湖场景中最常用的写入模式之一。当目标表没有主键约束时——这正是所有湖仓格式的共同特性——MERGE INTO 成为表达"插入或更新"语义的标准方式。

在 v1.5.3 之前,DuckDB-Iceberg 用户需要通过先查询、再判断、然后分别执行 INSERT 或 UPDATE 的方式来实现类似功能,这不仅代码冗长,而且无法保证原子性。现在,一条 MERGE INTO 语句即可完成全部操作。

代码示例

假设我们有一个人员信息表:

-- 创建 Iceberg 表
ATTACH 'my_warehouse' AS my_datalake (TYPE iceberg);
CREATE TABLE my_datalake.default.people (
    id INTEGER,
    name VARCHAR,
    salary FLOAT
);

-- 插入初始数据
INSERT INTO my_datalake.default.people
    VALUES (1, 'John', 92000.0), (2, 'Anna', 100000.0);

-- 查看当前数据
SELECT * FROM my_datalake.default.people ORDER BY id;

输出:

┌───────┬─────────┬──────────┐
│  id   │  name   │  salary  │
│ int32 │ varchar │  float   │
├───────┼─────────┼──────────┤
│     1 │ John    │  92000.0 │
│     2 │ Anna    │ 100000.0 │
└───────┴─────────┴──────────┘

现在,我们想要同时更新 John 的薪资并新增一名员工 Sarah:

MERGE INTO my_datalake.default.people AS target
    USING (
        FROM (VALUES
            (1, 'John', 105000.0),
            (3, 'Sarah', 95000.0)
        ) t(id, name, salary)
    ) AS upserts
    ON (upserts.id = target.id)
    WHEN MATCHED THEN UPDATE
    WHEN NOT MATCHED THEN INSERT;

查询结果确认操作已生效:

SELECT * FROM my_datalake.default.people ORDER BY id;
┌───────┬─────────┬──────────┐
│  id   │  name   │  salary  │
│ int32 │ varchar │  float   │
├───────┼─────────┼──────────┤
│     1 │ John    │ 105000.0 │
│     2 │ Anna    │ 100000.0 │
│     3 │ Sarah   │  95000.0 │
└───────┴─────────┴──────────┘

高级用法:DELETE 分支

MERGE INTO 还支持 WHEN MATCHED THEN DELETE 分支,可以在同一条语句中同时处理更新和删除操作:

MERGE INTO my_datalake.default.people AS target
    USING (VALUES (2, 'Anna', 0.0)) AS changes(id, name, salary)
    ON (changes.id = target.id)
    WHEN MATCHED AND changes.salary = 0.0 THEN DELETE
    WHEN MATCHED THEN UPDATE
    WHEN NOT MATCHED THEN INSERT;

DuckDB-Iceberg 的 MERGE INTO 采用 merge-on-read 语义,写入时将删除位置信息记录到 Iceberg 表中,读取时再合并。这种方式避免了重写整个数据文件的开销,特别适合频繁更新的大表。

二、ALTER TABLE:完整的 Schema 演化能力

功能概述

在 v1.4 版本中,DuckDB-Iceberg 的一个主要限制就是不支持模式演化(Schema Evolution)。这意味着一旦表被创建,其结构就无法修改——无法添加列、重命名列或删除列。对于生产环境的数据湖来说,这显然是不可接受的。

v1.5.3 彻底解决了这个问题。现在 ALTER TABLE 支持以下操作:

操作说明是否支持
RENAME TABLE重命名表
ADD COLUMN添加新列
RENAME COLUMN重命名列
DROP COLUMN删除列
SET format-version设置格式版本

代码示例

-- 创建测试表
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;

-- 设置格式版本为 V3
ALTER TABLE my_datalake.default.renamed_table
    SET ('format-version' = 3);

-- 查询结果
SELECT * FROM my_datalake.default.renamed_table ORDER BY col1;
┌───────┬──────────┐
│ col1  │   name   │
│ int32 │ varchar  │
├───────┼──────────┤
│     1 │ Andy     │
│     2 │ Bob      │
│     3 │ Claire   │
│     4 │ Mr. Duck │
└───────┴──────────┘

原理说明

ALTER TABLE 在底层更新 Iceberg 表的 current-schema-id,所有变更均通过 Iceberg REST Catalog 写入。由于 Iceberg 模式演化是纯元数据操作,数据文件不会被重写,因此执行速度极快且不影响现有数据。其他 Iceberg 兼容引擎(如 Spark、Trino)在下次查询 LoadTableInformation 端点时会立即看到这些变更。

三、bucket 与 truncate 分区变换

功能概述

Iceberg 规范定义了一系列分区变换(Partition Transforms),用于决定数据文件在磁盘上的布局方式。v1.5.3 新增了对 buckettruncate 两种变换的支持。

  • bucket(N, col):将列的值哈希到 N 个桶中。适合高基数列的稳定分区,例如用户 ID。
  • truncate(W, col):按列值的前 W 个字符(字符串)或列值向下取整到 W 的倍数(数值)进行分组。适合前缀分区,例如国家代码。

代码示例

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');

使用 iceberg_metadata 函数验证分区效果:

SELECT file_path, record_count
FROM iceberg_metadata(my_datalake.default.events)
WHERE content = 'EXISTING';

更新和删除操作在 bucket/truncate 分区表上同样受支持,采用 merge-on-read 语义和位置删除。

四、Iceberg Schema Properties 管理

功能概述

Iceberg 目录允许在模式(Schema/Namespace)级别附加任意的键值属性。这些属性通常用于记录所有权信息、描述、默认存储位置或其他适用于同一 Schema 下所有表的元数据。

v1.5.3 提供了三个新的表函数来管理 Schema 属性:

函数名功能
set_iceberg_schema_properties设置 Schema 属性
iceberg_schema_properties读取 Schema 属性
remove_iceberg_schema_properties删除 Schema 属性

代码示例

-- 设置 Schema 属性
CALL set_iceberg_schema_properties(my_datalake.default, {
    'owner': 'analytics-team',
    'description': 'Default analytics schema'
});

-- 读取 Schema 属性
SELECT * FROM iceberg_schema_properties(my_datalake.default);
┌─────────────┬──────────────────────────┐
│     key     │          value           │
│   varchar   │         varchar          │
├─────────────┼──────────────────────────┤
│ owner       │ analytics-team           │
│ description │ Default analytics schema │
└─────────────┴──────────────────────────┘
-- 删除 Schema 属性
CALL remove_iceberg_schema_properties(
    my_datalake.default,
    ['description']
);

Schema 属性通过 Iceberg REST Catalog 写入,任何连接到同一 Catalog 的 Iceberg 引擎都能立即看到更新。返回值为剩余的 Schema 属性数量。

五、Iceberg V3 格式支持

V3 新特性

Iceberg V3 规范引入了多项重要新特性,DuckDB-Iceberg 现在对以下功能同时支持读写:

特性说明
VARIANT 数据类型半结构化数据支持,类似 JSON
TIMESTAMP_NS 数据类型纳秒级时间戳
Schema 级默认值列的默认值定义
二进制删除向量比 V2 的位置删除文件更紧凑
行血统追踪数据行来源追踪

其中最具实际意义的是二进制删除向量。在 V2 表中,删除操作写入 Parquet 格式的位置删除文件;在 V3 表中,同样的信息以更紧凑的二进制格式编码为 Puffin 文件。DuckDB 会根据表的 format-version 自动选择正确的格式。

代码示例

-- 创建 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 │
└───────┴──────────────────┴───────────────────────────────┘

使用 iceberg_metadata 验证删除向量的格式:

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      │
└──────────────────┴──────────────────┴─────────────┘

注意GEOMETRY 类型和 Unknown 类型目前尚未在 DuckDB-Iceberg 中支持,团队计划在 DuckDB v2.0.0 中添加。

六、与传统方案的对比

特性DuckDB-Iceberg v1.5.3Apache Spark + IcebergTrino + Iceberg
部署复杂度嵌入式,无需集群需要 Spark 集群需要 Trino 集群
查询启动时间毫秒级秒级(需启动 Executor)秒级
MERGE INTO✅ 完整支持✅ 完整支持✅ 完整支持
ALTER TABLE✅ 完整支持✅ 完整支持✅ 完整支持
V3 格式✅ 读写支持✅ 读写支持⚠️ 部分支持
bucket/truncate✅ 写入 + 读取✅ 完整支持✅ 完整支持
Schema Properties✅ 完整管理✅ 完整管理✅ 读取支持
VARIANT 类型✅ V3 支持❌ 原生不支持❌ 原生不支持
安装大小~50MB数 GB数百 MB
单机海量数据处理✅ 优秀(列式向量化)⚠️ 需要分布式⚠️ 需要分布式
Python 集成✅ 原生支持✅ PySpark❌ 需 JDBC

DuckDB-Iceberg 的最大优势在于嵌入式架构带来的低延迟体验。不需要启动任何集群,一条 ATTACH 命令即可连接到 Iceberg REST Catalog,然后用熟悉的 SQL 进行查询和写入。

七、未来路线图

根据官方博客,DuckDB-Iceberg 的后续开发重点包括:

  1. UPDATEDELETEMERGE 的更多优化:进一步提升写入性能
  2. DuckDB v2.0.0 中的 GEOMETRY 类型支持:补全 Iceberg 类型支持
  3. 更深度的 Quack 协议集成:通过 Quack 远程访问 Iceberg 表
  4. DuckLake 中基于 Iceberg 的 Catalog 支持:统一湖仓管理

八、变现建议

DuckDB-Iceberg v1.5.3 的新特性为数据团队提供了多种变现路径:

1. 构建轻量级数据湖仓查询服务 利用 DuckDB 的嵌入式特性,为中小团队提供替代 Spark/Trino 的轻量级 Iceberg 查询方案。按查询量或并发用户数收费,月费 $500-$2000/团队。

2. 数据管道自动化工具 基于 MERGE INTOALTER TABLE 构建自动化 ETL/ELT 工具,帮助企业管理 Iceberg 表的增量更新和模式演化。SaaS 订阅模式,$200-$800/月。

3. 培训和咨询 围绕 DuckDB-Iceberg 的最佳实践、性能调优和架构设计提供企业培训。单次工作坊 $3000-$8000。

4. 开源周边工具开发 开发 DuckDB-Iceberg 的管理 UI、监控面板或 CI/CD 集成工具,通过开源社区版 + 企业版(含高级功能)变现。

5. 云原生数据湖管服务 在 AWS/GCP/Azure 上部署基于 DuckDB-Iceberg 的托管查询服务,利用 Serverless 架构实现成本优势,预留给客户的利润空间约 30-50%。


总结

DuckDB v1.5.3 中的 Iceberg 扩展更新是里程碑式的。MERGE INTO 提供了原子化的 Upsert 语义,ALTER TABLE 解除了模式演化的限制,bucket/truncate 分区变换带来了更灵活的数据布局策略,而 Iceberg V3 支持则将 DuckDB 推向了湖仓技术的最前沿。

对于数据工程师和架构师来说,这意味着可以用一个不到 50MB 的嵌入式数据库,完成过去需要分布式集群才能实现的 Iceberg 管理工作。无论是本地开发、CI/CD 测试还是小规模生产环境,DuckDB-Iceberg 都是一个极具吸引力的选择。

立即升级到 DuckDB v1.5.3,体验这些新特性吧!

📺 Watch video tutorials → DuckDB Lab YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计