2026年10月21日,DuckDB 团队宣布发布 DuckDB 2.0.0 LTS,代号“Cinnamon Teal”(Spatula cyanoptera),名字取自一种分布于美洲西部的红褐色鸭子。与侧重稳定与向后兼容的 DuckDB 1.0 不同,DuckDB 2.0.0 是一次以特性为主的版本,带来更完善的 Quack 协议、异步 I/O、一等 VARIANT 类型、agent 模式、触发器与可扩展 SQL 解析器等,基于 DuckDB 1.5 以来近 20,000 次提交构建而成。团队会先概述亮点,再说明破坏性变更与迁移方式;更多细节见“A Preview of DuckDB v2.0”及 GitHub 发布页。
正文
我们自豪地发布 DuckDB 2.0.0,代号“Cinnamon Teal”,其名取自Spatula cyanoptera(一种分布于美洲西部的红褐色鸭子)。
如果说 DuckDB 1.0 主要关注稳定性和向后兼容,那么 DuckDB 2.0 首先是一个功能版本。在这篇博客中,我们总结亮点,然后介绍破坏性变更及迁移方法。我们在“DuckDB v2.0 预览”中展示了许多新功能,并对其逐一做了更深入的介绍。完整发布说明见 GitHub 上的发布页面。
提示:安装新版本请访问安装页面。CLI 和 Python 等客户端已可用;Go、R、Java 等其他客户端库及部分扩展因需要额外改动和评审,可能要过几天才发布。
目前共有三条 DuckDB 产品线可用。DuckDB 2.0 是长期支持(LTS)版本,支持至少到 2027 年 10 月。上一个 LTS 版本 1.4 仍受支持,直到 2026 年 11 月底。为平稳过渡并留出充足测试时间,我们把 DuckDB 1.5 的支持期延长到 2027 年 3 月,实际上也把它变成了 LTS 版本。详见发布日历。
为方便查阅所有新功能的文档,我们还发布了“2.0 文档中的新内容”页面,把下面每个功能都链接到对应文档。
DuckDB 作为服务器
DuckDB 从第一天起就是一个进程内数据库。在 5 月发布的 DuckDB 1.5.3 中,我们引入了 Quack 协议,让你可以在传统的客户端-服务器架构中运行 DuckDB;同时推出的 quack 扩展实现了 DuckDB 的原生协议,因此任何 DuckDB 进程都能对外提供其数据库服务。
Quack 与 CONNECT
DuckDB 2.0.0 搭载了打磨后的 Quack 协议,性能和稳定性大幅提升。它还引入了新的 CONNECT 语句,可一条命令挂载并切换到远程数据库,易用性更好:
DuckDB 服务端
CALL quack_serve(token = 'my_token');
CREATE TABLE events (date DATE, name VARCHAR);
INSERT INTO events VALUES ('2027-04-07', 'DuckCon #8');
quack 客户端配置如下:
DuckDB 客户端
ATTACH 'quack:localhost' AS qk (TOKEN 'my_token');
CONNECT qk;
SELECT * FROM events;
查询返回一行:date 为 2027-04-07,name 为 DuckCon #8。
为简洁,本例在同一台机器上同时运行服务端和客户端。默认情况下,服务端只监听 localhost。要接受其他机器的连接,应让服务端保持在 localhost,并置于 TLS 终止反向代理之后。直接暴露服务端的 allow_other_hostname 选项仅用于测试。
CONNECT 语句不限于 Quack:它可以把会话指向任何支持该语句的远程数据库,包括 PostgreSQL 和 MySQL。对于这些数据库,新的远程下推优化器会把查询直接发到远程服务器,而不是把表拉过来。
CLI 中的 Quack
CLI 也提供了快捷方式来搭建 Quack 客户端-服务器组合:新的 -serve 和 -connect 参数可直接从命令行启动 Quack 服务端或连接服务端:
CLI 服务端
duckdb -serve
CREATE TABLE t AS SELECT 42 AS x;
CLI 客户端
duckdb -connect
FROM t;
返回 x 为 42(类型 int32)。
性能
异步 I/O
DuckDB 2.0 为读取 Parquet 和 CSV 文件引入了异步 I/O。I/O 层现在可独立于查询处理层扩展,在后台发起读取并让多个请求同时在线。这意味着远程读取的并行度大幅提高,在 s3:// 等对象存储上的查询也显著变快。
我们在一篇专门的博客文章中详细介绍了设计,并展示了启用和未启用异步 I/O 时一次查询过程中的网络吞吐量:
网络吞吐量。利用率越高、运行时间越短越好。
VARIANT 类型成为一等公民
VARIANT 类型用于存储 JSON 之类的半结构化、无模式数据,同时保持列式执行的性能。可以把它看作加强版 JSON:VARIANT 列的每一行都能存储不同形状的数据,但与 JSON 不同,它不是文本格式。
DuckDB 1.5 已引入 VARIANT,DuckDB 2.0 则将其全面集成。2.0 会检测数据中的公共结构并“切分”(shreds)它,因此压缩率高、执行快。整条流水线端到端可用:从存储中直接切分执行,到 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), variant_keys(payload) FROM events;
结果中两行 variant_type(payload) 均为 OBJECT;variant_keys(payload) 分别为 [user] 和 [event, x, y]。
CLI 中的代理模式
Claude Code、Codex 和 Cursor 等编码代理越来越多地代表用户驱动 DuckDB CLI。2.0 的 CLI 会检测自己是否被代理运行,并切换到代理模式:它打印紧凑的 Markdown 表格而不是填充的方框,明确说明结果是否被截断,提前终止失控的查询,并以 JSON 报告错误。
长时间运行的 SQL 查询在运行前会报告预估成本,让代理决定是否等待。我们在 TPC-H 上的实验中,代理模式让模型需要读取的 DuckDB 输出量减少了 59%。手动测试时可用 -agent 标志开启,也可让代理用 -no-agent 标志关闭。
可扩展性
全新的 SQL 解析器
DuckDB 一直使用源自 PostgreSQL 的解析器。在 2.0 中,我们自己的现代化、可扩展 PEG 解析器成为默认解析器;它曾在 DuckDB 1.5 中作为可选功能首次亮相。扩展现在能挂接到语法本身(这一想法最早在我们的运行时可扩展解析器研究中探索),因此未来会出现提供全新 SQL 语法的扩展。错误消息现在带有精确的源码位置。详见 PEG 解析器文档。新解析器被设计为与旧解析器兼容,你应该不会察觉到替换。如果发现问题,请提交 issue。同时,可通过 SET enable_legacy_parser = true; 用 legacy_parser 扩展切换回旧解析器。
旧解析器从 2018 年 DuckDB 第一次提交起就开始服务。它的语法用 YACC/Bison 生成,产生 LALR(1) 解析器,因此随着 DuckDB SQL 增长,即使很小的添加也可能与现有规则冲突,引发 shift/reduce 或 reduce/reduce 冲突。PEG 语法按书写顺序尝试备选方案,从而避免了这一点,添加语法不再有破坏无关规则的风险。Python 在 3.9 版也出于同样的灵活性转向了基于 PEG 的解析器。
一次编写扩展并自行托管
DuckDB 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;
仓库由名称、URL 前缀和一个或多个受信任的 RSA 公钥组成,这些公钥用于签名从该仓库提供的扩展。前缀可以指向 DuckDB 能读取的任何位置:本地路径、https、s3 等。在 CREATE 时,DuckDB 会获取仓库的公钥并固定到仓库定义中,打印每个密钥的 SHA-256 指纹,以便你与带外发布的指纹比对。
该功能仍在开发中,语法未来可能变化。详见 Extension Repositories 页面。
核心扩展更新
DuckDB 的核心扩展也带来了许多新功能、修复和性能优化。数量太多无法一一列出,这里只重点介绍几项,随后几周会有专门博客文章介绍:
- 我们发布了 DuckLake 1.1,包含新规范和参考实现。
- Iceberg 扩展正努力成为功能完整的实现。为此,它增加了 REST catalog
ATTACH选项,可从命名空间推导表位置,或从 catalog 返回的metadata-location文件加载表元数据;其他选项控制 URL 前缀和命名空间分隔符。 - Delta 扩展现在能用
CREATE TABLE创建 Delta 表(包括分区和 Delta 表属性),支持时间旅行,并改进了VARIANT支持。
SQL 功能
触发器
DuckDB 2.0 带来了触发器:支持 CREATE TRIGGER 语句,包含 BEFORE 和 AFTER 触发器、FOR EACH ROW 和 FOR EACH STATEMENT、通过 REFERENCING OLD/NEW TABLE 的过渡表、每个事件多个触发器、在被触发表上使用 RETURNING,以及 DROP TRIGGER。经典用例是审计表,触发器记录表修改带来的变化。某些组合(如 BEFORE ... FOR EACH ROW 和 UPDATE ... FOR EACH ROW 触发器)尚不支持;参见限制。
例子:
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;
触发器还能增量维护物化视图!例如,要在表 age_statistics 中跟踪每个国籍的平均年龄,可以用两个触发器(一个插入、一个删除),并把总和与计数连同平均值一起存储。
创建示例表:
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);
插入时,按国籍聚合新行并合并进 age_statistics,为每个新国籍添加一行:
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);
删除时,我们减去被删除的行,并在某个国籍的最后一个人离开后删除该国籍:
CREATE TRIGGER people_delete AFTER DELETE ON people REFERENCING OLD TABLE AS o FOR EACH STATEMENT MERGE INTO age_statistics s USING (SELECT nationality, sum(age) AS sum_age, count(*) AS num_people FROM o GROUP BY nationality) d ON s.nationality = d.nationality WHEN MATCHED AND s.num_people = d.num_people THEN DELETE 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);
看人员增减:
INSERT INTO people VALUES ('Alice', 'US', 34), ('Bram', 'NL', 27), ('Csilla', 'HU', 45), ('Dave', 'US', 58), ('Eva', 'NL', 19), ('Femke', 'NL', 41);
FROM age_statistics ORDER BY nationality;
第一次查询结果(nationality、sum_age、num_people、average_age 分别为 varchar、int32、int32、double):HU 为 45、1、45.0;NL 为 87、3、29.0;US 为 92、2、46.0。
DELETE FROM people WHERE name = 'Alice';
DELETE FROM people WHERE nationality = 'HU';
FROM age_statistics ORDER BY nationality;
结果变为:NL 为 87、3、29.0;US 为 58、1、58.0(HU 已消失)。
触发器让我们离物化视图更近一步,这是路线图“未来工作”中的一个长期项目。
SQL 方言新增
一如既往,DuckDB 友好的 SQL 方言持续扩充。本发布周期的一些亮点:
$variable简写让你在大多数允许表达式的地方写$x,而不用getvariable('x')。- 数据修改 CTE让
INSERT、UPDATE、DELETE和COPY可作为流水线步骤。 NEAREST连接把 top-k 相似度搜索变成连接子句,适合向量和嵌入负载。- 嵌套模式允许模式内再建模式,例如
CREATE SCHEMA finance.reports。 - JSON 修改函数:
json_set、json_insert、json_replace和json_remove返回修改后的 JSON 文档副本(另见我们关于 JSON 补丁的博客文章)。
此外:DuckDB 2.0.0 支持 SQL 标准的 FETCH FIRST,并允许在 GROUP BY 子句中使用 UNNEST。它还为多行匹配的情况确保了定义良好的 MERGE 和 UPDATE ... FROM 语义。
迁移到 2.0
DuckDB 2.0 带有少量破坏性变更。对大多数用户来说升级很直接,但运行大量 DuckDB 进程的复杂部署需要更谨慎。存储格式是最可能的问题来源:2.0 之前的 DuckDB 版本无法读取 DuckDB 2.0 创建的数据库文件。DuckDB 2.0 仍可读写你现有的数据库文件,这些文件也仍能被旧版本读取。我们建议先在影子系统上测试 2.0.0,再切换生产环境。如果你的工作负载是关键任务,可能要等 11 月发布的第一个补丁版本(参见发布日历)。
完整细节见迁移指南。
内部变化与弃用
编译器要求
在长期停留在 C++11 之后,DuckDB 2.0 现在要求 C++17 编译器。
我们还把发布构建的默认编译器切换为 Clang。在 Linux 和 macOS 上,DuckDB 及其核心扩展都用 Clang 构建。在 Windows 上,CLI 用 Clang 构建,而大多数核心扩展仍用 MSVC。未来我们可能把更多 Windows 构建迁移到 Clang,但这需要进一步改动。
不依赖 ICU 的时区、日历和排序规则
时区感知的时间戳、日历和排序规则一直由 ICU 库提供支持。在 2.0 中,ICU 库被完全移除:icu 扩展现在自行实现时区、日历和排序规则,时区数据直接来自 IANA 数据库并压缩到约 45 kB。
一切仍像以前一样工作,原生实现也更快。如果发现任何问题,请告诉我们!
弃用
Lambda 语法
旧的单箭头 lambda 语法(x -> x + 1)现在默认禁用。请改用 Python 风格语法:
SELECT list_transform([1, 2, 3], lambda x: x + 1);
你可以用 SET lambda_syntax = 'enable_single_arrow'; 临时重新启用旧语法。该标志和单箭头语法将在 v2.1 中彻底移除。
合并构建
合并构建把 DuckDB 的全部源码打包进单个头文件和单个源文件,现已弃用。合并文件已变得过大,在大多数情况下不再实用。
逐步淘汰 macOS x86_64
我们正逐步把 osx_amd64 从完全支持的平台列表中淘汰。目前 DuckDB 和核心扩展仍为该平台构建并受支持,但社区扩展不再为该平台构建。DuckDB 2.1 将停止支持 osx_amd64。
总结
以上是一些亮点,但本次发布还有更多功能和改进。自 DuckDB 1.5 以来,许多贡献者提交了近 20,000 次 commit,带来了更小的特性以及性能和易用性改进。完整的发布说明可在 GitHub 上找到。
我们要感谢社区提出详细的问题报告和反馈,推动了本次发布,并特别感谢我们的贡献者!

评论
这次讨论几乎一边倒地肯定 DuckDB 2.0:更强的性能、多核扩展、客户端-服务器架构和 Triggers,都被视为让它从嵌入式分析引擎更进一步,接近“一体化数据仓库”。真正的差异不在“好不好”,而在落地场景与具体收益——已经有人把它用于十亿级记录关联和生产分析,也有人虽然认可却找不到公司内部用例,并对异步 I/O 的实际价值提出疑问。
认为 2.0 的性能和云上扩展性明显更强
- 多位使用者反馈它在规模合适的云虚拟机、S3 与 Iceberg 查询中都很快;有人实测在多核 EC2 上,2.0 能把 CPU 利用率推到约 128–192 vCPU,而旧版大约 64 vCPU 就触及瓶颈,因此大规模计算完成得更快。(较多人)
认为客户端-服务器架构和 Triggers 让它接近“一体化数据仓库”
- 有人把客户端-服务器架构称为游戏规则改变者,认为它能替代许多原本跑在多台大 CPU、大内存服务器上的数据栈;也有人对 Triggers 感到兴奋,觉得 DuckDB 已经很接近装在盒子里的完整数据仓库。(少数人)
认为它不只是分析引擎,也很适合数据摄取与重塑
- 有用户强调,除了分析,它在摄取和重塑数据方面异常灵活,用途远不止当查询引擎。(少数人)
对异步 I/O 的实际收益和覆盖格式存疑
- 有读者注意到发布说明只提到为 Parquet 和 CSV 引入异步 I/O,于是追问这是否意味着 DuckDB 自有格式本来就支持,或者恰恰相反;也有人直接问,对这两类文件做异步 I/O 到底有什么好处。(少数人) > 公告里只说 Parquet 和 CSV 支持异步 I/O,那是不是意味着 DuckDB 自己的格式早已支持,或者反而不支持?(@fragebogen)
认可 DuckDB 强大,却苦于找不到公司内部落地场景
- 有人坦言很想用 DuckDB,但在公司里一直没找到合适用例,于是向社区请教大家究竟拿它做什么;这让“它到底适合什么业务场景”成为一个实际追问。(少数人) > 我很想用 DuckDB,可在公司里就是找不到用例,你们到底用它做什么?(@mikkelam)
有价值的补充
- RobinL 用开源记录关联工具 Splink 做了规模化验证:借助 DuckDB 2.0,他们能在约 10 分钟内处理 10 亿条记录、完成 100 亿次记录比较,并称这可能是同类关联引擎中最快的;性能主要来自把大计算交给 DuckDB。
- nicoritschel 已经在小型组织中把 alpha 版当集中式分析服务器跑在生产环境;他还写了扩展,让客户端能通过 Postgres 协议或 Arrow Flight 连接 DuckDB。相比 DuckDB 自带的 Quack 协议,Postgres 协议和 ADBC 的客户端支持更广,很多数据工具都能直接接上。
- 多核扩展上有个反直觉细节:2.0 之前速度大约到 64 vCPU 就触顶,2.0 能吃到约 128–192 vCPU,但超过约 192 vCPU 后通常仍看不到进一步加速,不是核数越多收益越高。(RobinL)