DuckDB 2.0 深度解读:从嵌入式数据库到服务化数据库的质变

DuckDB 2.0 LTS 不只是又一次功能堆叠。它把 Quack 服务化协议、异步 I/O、一等 VARIANT 类型、触发器、PEG 解析器写进核心,重塑了嵌入式数据库的边界。本文逐层拆解 2.0 的架构变化,每个特性都配可运行代码,附破坏性变更迁移清单。

为什么 2.0 不是又一次功能堆叠

DuckDB 1.0 的关键词是稳定与向后兼容,1.5 把 Quack 协议、VARIANT、Agent 可观测性铺了底。而 2.0 LTS(代号 “Cinnamon Teal”,取自美洲西部的彩头鸭 Spatula cyanoptera)自 1.5 以来累计近 2 万个 commit,官方把它定位成功能发布,不是修补。

判断一篇版本解读有没有"深度",标准不该是"列了多少新特性",而是能不能说清每个特性改变了数据库的哪一层边界。2.0 真正质变的点,是把 DuckDB 从一个进程内嵌的分析引擎,推成了一个可以同时以嵌入、服务、远程三种形态运行的数据库:

  • 服务化:Quack 协议 + CONNECT,任意 DuckDB 进程可把库服务出去,还能连 PostgreSQL/MySQL 并做远程下推
  • 异步 I/O:读层与查询层解耦,对象存储(s3://)上的查询吞吐显著提升
  • VARIANT 成头等公民:半结构化数据"shredding"后列式执行,JSON 性能直追原生列
  • 可插拔解析器:内置 PEG 解析器,扩展能自定义 SQL 语法
  • 触发器:CREATE TRIGGER 补齐审计表与物化视图增量维护的最后一块
  • Agent 模式:CLI 检测到被 AI 编程代理驱动时自动切紧凑输出,TPC-H 实测减少 59% 输出

下面逐层拆解,代码均用 DuckDB 2.0.0 CLI 实测(非 1.5)。

1. 服务化:Quack 协议与 CONNECT

DuckDB 从第一天起就是进程内嵌数据库。1.5.3 引入 Quack 协议后,2.0 把它做成了打磨过的一等能力——任何 DuckDB 进程都能用 quack_serve 把库服务出去,别的进程用 ATTACH ... 'quack:localhost' 连上。

服务端(起服务 + 写入):

CALL quack_serve (token = 'my_token');
CREATE TABLE events (date DATE, name VARCHAR);
INSERT INTO events VALUES ('2027-04-07', 'DuckCon #8');

客户端(连接 + 切换 + 查询):

ATTACH 'quack:localhost' AS qk (TOKEN 'my_token');
CONNECT qk;
SELECT * FROM events;
datename
2027-04-07DuckCon #8

安全注意:默认只监听 localhost。跨机器访问应保持服务端在 localhost、前置 TLS 终结反向代理;allow_other_hostname 仅供测试。

CONNECT 的价值不止于 Quack——它能指向任何支持该协议的远程库,包括 PostgreSQL 和 MySQL。对这些目标,2.0 的远程下推优化器会把查询直接推到远端执行,而不是把整张表拉过网络回来再算。这一步把 DuckDB 从"本地分析引擎"扩展成"能跨数据源做联邦查询"的角色,是 SaaS 多租户、混合数据场景的关键一环(延伸阅读:DuckDB v2.0 Server Mode 架构)。

CLI 里还有快捷入口:duckdb -serve 起服务、duckdb -connect 直连,省得手写 quack_serve。

2. 性能:异步 I/O

2.0 给 Parquet、CSV 以及 DuckDB 原生存储格式引入异步 I/O:I/O 层不再与查询处理层绑定,读操作在后台发起、多个请求并行在途。对本地盘是"锦上添花",对 s3:// 这类高延迟对象存储则是数量级改善——网络吞吐利用率更高、查询运行时间更短。

设计细节官方单独发了一篇博客讲清(含启用前后吞吐对比曲线),这里不展开。记住一句:读远端大文件时,2.0 的异步 I/O 是你白拿的性能红利。

3. VARIANT 成为头等公民

VARIANT 在 1.5 已引入,但 2.0 把它完整集成了:存的是 schema-less 半结构化数据(典型如 JSON),却保留列式执行的性能——可理解为"加强版 JSON"。每一行可以是不同形状的数据,但它不是文本格式。

核心机制是 shredding(解构):2.0 检测数据中的公共结构,把它"打散"成列式存储,于是既能高压缩、又能快速执行。流水线端到端打通——从解构后的存储执行,到 Parquet 的 VARIANT 读写,再到一族 variant_* 函数:

CREATE TABLE events (payload VARIANT);
INSERT INTO events VALUES
  ('{"user": {"id": 42, "tags": ["a","b"]}}'::JSON::VARIANT),
  ('{"event":"click","x":10,"y":20}'::JSON::VARIANT);

SELECT variant_type(payload) AS t, variant_keys(payload) AS k FROM events;
tk
OBJECT[user]
OBJECT[event, x, y]

对做事件流、埋点、半结构化日志分析的人,这一条意味着:不用再为 JSON 的灵活性牺牲列式性能。

4. Agent 模式:为 AI 编程代理而设计

这是 2.0 里最"时代"的功能。当 CLI 检测到被 Claude Code、Codex、Cursor 这类编程代理驱动时,自动切到 Agent 模式:

  • 输出紧凑 Markdown 表(而非填充格子的盒子)
  • 明确标注结果是否被截断
  • 提前中止失控查询
  • 报错用 JSON
  • 长查询先报告预估成本,让代理决定是否等

官方在 TPC-H 上实测:Agent 模式让模型要读的 DuckDB 输出减少 59%。手动测试可用 -agent,让代理关闭用 -no-agent:

duckdb -agent :memory: "SELECT 42 AS x;"

实测输出是紧凑形式:

| x:INTEGER |
|---|
| 42 |

这与 DuckDB 做 AI 数据接口的方向一脉相承(延伸阅读:用 DuckDB 构建 AI Agent 大脑、DuckDB MCP Server 指南)。

5. 可扩展性:全新 PEG 解析器

DuckDB 历来用从 PostgreSQL 派生的解析器。2.0 起,自家的现代 PEG 解析器(1.5 里作为 opt-in 试用)成为默认。扩展现在能钩进语法本身——官方预期会有暴露全新 SQL 语法的扩展出现。报错也带上了精确的源码位置。

为什么换?老解析器基于 YACC/Bison 生成的 LALR(1) 语法,随着 DuckSQL 扩张,任何小改动都可能撞上既有规则、引发 shift/reduce 或 reduce/reduce 冲突。PEG 按书写顺序尝试各分支,新增语法不再有不破坏无关规则的风险(Python 3.9 也是为此转向 PEG)。新解析器与旧的保持兼容,出可用 SET enable_legacy_parser = true; 切回(2.0 实测该开关存在且生效)。

自己写扩展、自己托管

C API v2 大幅增强,暴露了文件系统、表函数等组件给扩展。还能注册并托管可信扩展仓库——一个组织可签发自己的扩展并像内置一样安装加载:

SET allow_extension_repositories = 'allowed';
CREATE EXTENSION REPOSITORY my_repo WITH PREFIX 'https://extensions.example.org';
INSTALL AND LOAD my_ext FROM my_repo;

CREATE 时会拉取仓库的公钥并固化进仓库定义,打印每个 key 的 SHA-256 指纹供带外比对。该特性仍在演进,语法可能变。

6. 核心扩展更新

2.0 同时带了一批核心扩展的更新与性能优化,挑几个生产相关的:

  • DuckLake 1.1:新规格 + 参考实现
  • Iceberg:向"功能完整"推进,新增 REST catalog 的 ATTACH 选项(按 namespace 推导表位置,或从 catalog 返回的 metadata-location 文件读元数据)
  • Delta:支持 CREATE TABLE(含分区与 Delta 表属性)、时间旅行、改进 VARIANT 支持

7. 触发器:审计表与物化视图增量维护

2.0 交付了 CREATE TRIGGER,支持 BEFORE/AFTER、FOR EACH ROW / FOR EACH STATEMENT、经 REFERENCING OLD/NEW TABLE 的过渡表、单事件多触发器、被触发表上的 RETURNING、DROP TRIGGER。经典用例是审计表——记录一次表修改时发生了什么:

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、一个 DELETE)维护"按国籍统计平均年龄"的聚合表:

CREATE TABLE people (name VARCHAR, nationality VARCHAR, age INTEGER);
CREATE TABLE age_statistics (nationality VARCHAR PRIMARY KEY,
                             sum_age INTEGER, num_people INTEGER, average_age DOUBLE);

CREATE TRIGGER people_insert AFTER INSERT ON people
REFERENCING NEW TABLE AS n FOR EACH STATEMENT
MERGE INTO age_statistics s USING
  (SELECT nationality, sum(age) AS sum_age, count(*) AS num_people
   FROM n GROUP BY nationality) d
  ON s.nationality = d.nationality
  WHEN MATCHED THEN UPDATE SET sum_age = s.sum_age + d.sum_age,
       num_people = s.num_people + d.num_people,
       average_age = (s.sum_age + d.sum_age) / (s.num_people + d.num_people)
  WHEN NOT MATCHED THEN INSERT VALUES (d.nationality, d.sum_age, d.num_people, d.sum_age/d.num_people);

实测:插入 6 人后聚合表自动生成:

nationalitysum_agenum_peopleaverage_age
HU45145.0
NL87329.0
US92246.0

官方反复强调:触发器让 DuckDB 离"物化视图"这个长期路线图又近了一步(BEFORE … FOR EACH ROW 等少数组合暂不支持,见官方限制说明)。

8. SQL 方言扩充

2.0 的方言新增,挑实用项:

  • $ 变量简写:大多数表达式位置可写 $x 替代 getvariable('x')
  • 数据修改 CTE:INSERT/UPDATE/DELETE/COPY 可作为管道步骤
  • NEAREST join:把 top-k 相似检索变成 join 子句,向量/嵌入场景直接受益
  • 嵌套 schema:CREATE SCHEMA finance.reports; 支持 schema 里套 schema
  • JSON 变更函数:json_set / json_insert / json_replace / json_remove 返回修改后的 JSON 副本(详见 JSON 补丁函数实战)

实测嵌套 schema 与 JSON 变更(2.0 CLI):

CREATE SCHEMA finance;
CREATE SCHEMA finance.reports;
SHOW SCHEMAS;
-- memory | finance / main / reports 三行

SELECT json_set('{"a":1,"b":2}'::JSON, '$.c', '3'::JSON),
       json_remove('{"a":1,"b":2}'::JSON, '$.b');
-- {"a":1,"b":2,"c":3}   {"a":1}

另:2.0 支持 SQL 标准的 FETCH FIRST,并允许 UNNEST 出现在 GROUP BY 子句里;对多行命中时,MERGE 与 UPDATE ... FROM 的语义被定义得无歧义。

9. 迁移到 2.0:破坏性变更与注意项

升级对多数用户平滑,但多进程、生产级部署要看清这三处:

存储格式(最易踩坑)

2.0 之前的版本无法读取 2.0 创建的数据库文件;而 2.0 仍能读写旧文件、且旧文件对老版本保持可读。建议先在影子系统上验证再切生产;关键工作负载可等 11 月的首个补丁版本。

编译器要求

告别 C++11,2.0 起要求 C++17 编译器;Linux/macOS 发行构建默认改用 Clang(Windows 的 CLI 用 Clang,多数核心扩展仍是 MSVC)。

时区/日历/排序:去掉 ICU 依赖

2.0 彻底移除 ICU 库——icu 扩展现在自实现时区、日历、排序,时区数据直接由 IANA 数据库构建并压缩到约 45 kB,行为不变且更快。

弃用项

  • Lambda 旧单箭头语法(x -> x + 1)默认禁用,改用 Python 风格:

    SELECT list_transform([1, 2, 3], lambda x : x + 1);  -- [2, 3, 4]
    -- 旧语法 2.0 实测报:
    -- Binder Error: Deprecated lambda arrow (->) detected ...
    -- 可临时 SET lambda_syntax='ENABLE_SINGLE_ARROW' 切回,v2.1 彻底移除
    
  • amalgamation 构建(把整库源码压成单 header + 单 .c)弃用——已大到多数场景不再实用

  • macOS x86_64 逐步退场:当前仍构建支持,但社区扩展不再为其构建,v2.1 起停止支持

三条 LTS 线

版本支持到说明
1.42026 年 11 月底旧 LTS
1.52027 年 3 月官方延长,事实上的 LTS
2.02027 年 10 月新 LTS,Cinnamon Teal

小结与下一步

2.0 的核心不是"多了 X 个函数",而是数据库形态的扩展:嵌入、服务、远程三种运行方式共存;VARIANT + 触发器 + 异步 I/O 让"分析库"真正能干"事务库"的活;PEG 解析器 + C API v2 把可定制性打开给扩展作者。

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials