Featured image of post DuckDB 2.0.0 正式发布:从嵌入式工具到客户端-服务器架构的蜕变

DuckDB 2.0.0 正式发布:从嵌入式工具到客户端-服务器架构的蜕变

DuckDB 2.0.0 LTS 正式发布,代号 Cinnamon Teal。6 大核心特性:Quack 协议让 DuckDB 变身为服务器,异步 I/O 零代码改动提升 3 倍,VARIANT 类型压缩 62%,触发器、Agent 模式、新 SQL 解析器。附迁移指南与破坏性变更。

等了三年,DuckDB 终于从"嵌入式分析数据库"进化成了真正的"客户端-服务器架构"。

2026 年 10 月 21 日,DuckDB 2.0.0 LTS(代号 Cinnamon Teal,桂红茶色鸭)正式发布。这不是一次小版本迭代,而是一次架构层面的蜕变。Quack 协议让任何 DuckDB 进程都能变成服务器,异步 I/O 在不改一行代码的情况下把 S3 查询速度提升了 3 倍,VARIANT 类型让半结构化数据的查询性能暴涨 78 倍。

这篇文章会带你过一遍 2.0 的 6 大核心特性,看看哪些值得立刻上手,哪些迁移时要小心踩坑。

Quack 协议:DuckDB 变身为服务器

这是 2.0 最具颠覆性的特性。

过去 DuckDB 的定位是"嵌入式 SQLite 替代品",进程内运行,数据文件本地存储。Quack 协议打破了这个限制——任何 DuckDB 进程都可以作为客户端或服务器,基于 HTTP 协议通信,防火墙友好,支持认证和连接缓存。

三种连接方式

方式一:ATTACH + CONNECT(推荐)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-- 服务端启动
duckdb -serve

-- 客户端连接(另一个终端)
duckdb -connect

-- 或者在 SQL 中
ATTACH 'quack:server.example.com' AS qk(TOKEN 'MY_QUACK_TOKEN');
CONNECT qk;
SELECT count() FROM events;  -- 在服务器执行,结果流式返回
DISCONNECT;

这种方式会把整个会话路由到远程服务器,适合需要执行多条查询的场景。

方式二:ATTACH 作为完整 catalog

1
2
ATTACH 'quack:server.example.com' AS qk;
SELECT count() FROM qk.my_schema.events;  -- 通过表名限定

这种方式把远程服务器作为一个完整的 catalog,可以混合查询本地和远程数据。

方式三:quack_query() 函数

1
SELECT * FROM quack_query('quack:server.example.com', 'SELECT count() FROM events');

无状态单次查询,适合偶尔需要远程执行的场景。

实际应用场景

Quack 协议打开了很多新的可能性:

  • 数据湖查询:本地 DuckDB 连接远程 DuckDB 服务器,查询 S3 上的 Parquet 文件
  • 分布式分析:多个 DuckDB 节点组成集群,各自处理分片数据
  • 开发测试分离:开发环境用本地 DuckDB,生产环境连接远程服务器

不过要注意,Quack 目前还是早期阶段,生产环境部署前需要充分测试。

异步 I/O:零代码改动的 3 倍性能提升

这是最让我兴奋的特性。

DuckDB 2.0 重写了 I/O 层,将 I/O 与查询处理完全解耦。工作线程处理已到达的数据时,I/O 线程在后台继续预取。这意味着你的查询代码不需要任何修改,性能就会自动提升。

官方基准测试数据

场景v1.5.5(同步)v2.0.0(异步)提升
单文件 S3 Parquet(2.2GB)9.344s2.945s3.2x
多文件并行读取基准-~3x
SSD 本地冷读基准-1.5x
SSD 本地热读基准-无差异(已缓存)

S3 场景提升最明显,因为网络延迟是主要瓶颈。本地 SSD 热读已经很快了,异步 I/O 帮不上忙。

配置参数

异步 I/O 默认开启,通过 read_ahead_depth 控制预取深度:

1
2
3
4
5
-- 默认值 -1(自动,根据线程数调整)
SET read_ahead_depth = -1;

-- 关闭异步 I/O(回到 1.5 行为)
SET read_ahead_depth = 0;

支持格式

  • ✅ Parquet
  • ✅ CSV(未压缩、可寻址、UTF-8)
  • ✅ DuckDB 原生存储格式
  • ❌ JSON(仍使用同步 I/O)

JSON 还没支持异步 I/O,这是个遗憾。如果你的数据管道大量使用 JSON,可能需要等后续版本。

VARIANT 类型:半结构化数据的杀手锏

这是 Snowflake 早就有的特性,DuckDB 终于跟上了。

VARIANT 类型用于存储半结构化、无 schema 的数据(比如 JSON),但保持列式执行的性能。核心机制是"shredding"(分解)——自动检测常见结构,将 JSON 字段转为真实类型列存储。

性能对比(500 万事件测试)

指标JSON 字符串VARIANT(shredded)提升
磁盘占用224 MB85 MB62% 压缩
查询性能基准60ms78x 更快
与类型化列对比-60ms仅慢 20%

78 倍的性能提升,这个数字相当惊人。而且 shredded VARIANT 与真实的类型化列相比,只慢 20%,几乎可以忽略。

使用示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
CREATE TABLE events(payload VARIANT);

INSERT INTO events VALUES
  ('{"user": {"id": 42, "tags": ["a", "b"]}}'::JSON::VARIANT),
  ('{"user": {"id": 99, "tags": ["c"]}}'::JSON::VARIANT);

-- 查询嵌套字段
SELECT payload->'user'->'id' FROM events;

-- 过滤
SELECT * FROM events WHERE payload->'user'->'id' = 42;

每行可以存储不同形状的 JSON,自动分解为类型化列。支持 Parquet 读写,提供 variant_* 函数族。

对于日志分析、事件追踪这类场景,VARIANT 类型会大幅提升开发效率和查询性能。

触发器:自动响应表变化

DuckDB 2.0 终于支持触发器了。

支持的类型很全面:

  • BEFORE / AFTER
  • FOR EACH ROW / FOR EACH STATEMENT
  • REFERENCING OLD TABLE / NEW TABLE(转换表)
  • 每个事件多个触发器
  • RETURNING 子句
  • DROP TRIGGER

经典用例:审计表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
CREATE TABLE target(id INTEGER, val INTEGER);
CREATE TABLE audit(id INTEGER, old_val INTEGER, new_val INTEGER);

CREATE TRIGGER trg_audit AFTER UPDATE ON target
  REFERENCING OLD TABLE AS o NEW TABLE AS n
  FOR EACH STATEMENT
  INSERT INTO audit
  SELECT n.id, o.val, n.val
  FROM o JOIN n ON o.id = n.id;

INSERT INTO target VALUES (1, 10), (2, 20);
UPDATE target SET val = val + 10 WHERE id <= 2;

SELECT * FROM audit;
-- 结果:
-- id | old_val | new_val
-- 1  | 10      | 20
-- 2  | 20      | 30

性能对比(50 万行插入测试)

触发器类型相对开销
无触发器基准
FOR EACH STATEMENT1x
FOR EACH ROW3x

最佳实践很明确:优先使用 FOR EACH STATEMENT(批量处理),避免 FOR EACH ROW(逐行处理)。3 倍的性能差距,在大数据量场景下会很明显。

其他亮点

Agent 模式

CLI 内置 AI 代理模式,可以用自然语言描述需求,自动生成 SQL。需要配置 LLM API(如 OpenAI)。

1
2
3
duckdb --agent-mode
> "帮我统计每个国家的用户数量,按降序排列"
-- 自动生成并执行 SQL

这个功能还在早期阶段,但对于快速原型开发很有帮助。

新 SQL 解析器

扩展可以添加新的 SQL 语句、子句、关键字,不再局限于核心 SQL 语法。这为第三方扩展提供了更强大的能力,比如定义专属的 COPY TO 格式或特定领域的查询语言。

递归 CTE 性能提升

重写递归查询引擎,深层递归查询(如 20,000 次提交的祖先遍历)从 16 秒降到 0.4 秒,40 倍提升。

LTS 长期支持

DuckDB 2.0.0 是 LTS 版本,社区支持期限到 2027 年 10 月 22 日(1 年)。适合生产环境部署。

迁移指南:破坏性变更

升级 2.0 之前,这些破坏性变更必须了解。

1. 新存储格式(最重要)

DuckDB 2.0 使用新的默认存储格式。旧版本(< 2.0)无法读取 2.0 创建的数据库文件,但 2.0 可以读取旧格式(自动迁移)。

如果有多个 DuckDB 进程访问同一数据库,必须全部升级到 2.0。

2. Lambda 语法变更

1
2
3
4
5
-- 旧语法(1.x)
list_filter([1, 2, 3], x -> x > 1)

-- 新语法(2.0)
list_filter([1, 2, 3], (x) -> x > 1)  -- 参数需要括号

这个改动会影响所有使用 lambda 表达式的查询。

3. 除零错误

1
2
3
4
5
6
7
-- 旧行为:返回 NULL
SELECT 1 // 0;  -- NULL
SELECT 1 % 0;   -- NULL

-- 新行为:抛出错误
SELECT 1 // 0;  -- ERROR: division by zero
SELECT 1 % 0;   -- ERROR: division by zero

如果你的查询依赖除零返回 NULL 的行为,需要加上 NULLIF 处理。

4. ICU 扩展移除

时区、日历、排序规则现在由 icu 扩展处理,核心构建不再包含。自定义构建需要显式加载 icu 扩展。

5. 标识符大小写

identifier_case 设置行为变更,建议显式使用引号处理大小写敏感标识符。

总结

DuckDB 2.0 不是一次简单的版本升级,而是一次架构层面的蜕变。

Quack 协议让 DuckDB 从嵌入式工具变成了客户端-服务器架构,异步 I/O 在不改代码的情况下把性能提升了 3 倍,VARIANT 类型让半结构化数据的查询性能暴涨 78 倍,触发器补全了数据管道的最后一块拼图。

作为 LTS 版本,2.0 适合生产环境部署。但升级前一定要注意新存储格式和 Lambda 语法变更这些破坏性改动。

DuckDB 正在从"SQLite 的分析替代品"进化成"轻量级的分析型数据仓库"。这个定位变化,会让它在数据湖、实时分析、嵌入式 BI 这些场景下越来越有竞争力。

参考资源

Built with Hugo
Theme Stack designed by Jimmy