DuckDB 2.0.0 LTS “Cinnamon Teal”
TL;DR · AI 摘要
DuckDB 2.0.0 LTS发布,引入多项新特性如Quack协议优化、异步I/O和VARIANT类型,提升性能与扩展性。
核心要点
- Quack协议性能提升30%,稳定性增强支持生产环境部署
- 异步I/O特性使批量数据加载速度提升2倍
- LTS版本提供至2027年10月的长期技术支持
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- DuckDB 2.0.0 LTS
- 核心特性
- Quack协议优化
- 异步I/O
- VARIANT类型
- 版本支持
- LTS至2027年10月
- 1.5版本支持延长至2027年3月
金句 / Highlights
值得收藏与分享的关键句。
Quack协议性能提升30%,稳定性增强支持生产环境部署
异步I/O特性使批量数据加载速度提升2倍
LTS版本提供至2027年10月的长期技术支持
宣布 DuckDB 2.0.0 LTS “Cinnamon Teal” – DuckDB
搜索快捷键
cmd
+
k
|
ctrl
宣布 DuckDB 2.0.0 LTS “Cinnamon Teal”
2026-10-21
14 分钟阅读
DuckDB 团队
TL;DR:DuckDB 团队很高兴地宣布,今天我们将发布 DuckDB 2.0.0 LTS 版本,代号为 “Cinnamon Teal”(Spatula cyanoptera)。这是我们迄今为止最大的功能发布,包含了经过打磨的 Quack 协议、异步 I/O、一流的 VARIANT 类型、代理模式、触发器、可扩展的 SQL 解析器等,这些功能基于从 DuckDB 1.5 开始的近 20,000 次提交构建。
我们自豪地发布 DuckDB 2.0.0,代号为 “Cinnamon Teal”,以 Spatula cyanoptera(一种在美洲西部发现的鲜艳的红棕色鸭子)命名。
虽然 DuckDB 1.0 的重点是稳定性和向后兼容性,但 DuckDB 2.0 首要的是功能发布。在本文中,我们总结了亮点,然后介绍破坏性变更以及如何迁移。我们在 “A Preview of 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 版本。详情请参见发布日历。
为了帮助您在所有新功能的文档中找到方向,我们还发布了一篇 “What's New in the Docs for 2.0” 页面,该页面将下方每个功能链接到其对应的文档。
作为服务器的 DuckDB
DuckDB 自诞生以来一直是一个进程内数据库。在 2023 年 5 月发布的 DuckDB 1.5.3 中,我们引入了 Quack 协议,使您能够在传统的客户端-服务器设置中运行 DuckDB,同时引入了 quack 扩展,该扩展实现了 DuckDB 的原生协议,因此任何 DuckDB 进程都可以为其数据库提供服务。
Quack 和 CONNECT
DuckDB 2.0.0 提供了经过打磨的 Quack 协议,具有显著的性能和稳定性改进。它还引入了新的 CONNECT 语句,允许通过单个命令附加并切换到远程数据库,从而提升可用性:
markdownlint-disable MD001
#### 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
│
name
│
│
date
│
varchar
│
├────────────┼────────────┤
│
2027-04-07
│
DuckCon
#
8
│
└────────────┴────────────┘markdownlint-enable MD001
为了简化操作,此示例在相同机器上运行服务器和客户端。默认情况下,服务器仅监听本地主机。若需接受来自其他机器的连接,可将服务器保留在本地主机并置于终止TLS的反向代理之后。直接暴露服务器的allow_other_hostname选项仅用于测试目的。
CONNECT语句不仅限于Quack:它可以将您的会话指向任何支持该功能的远程数据库,包括PostgreSQL和MySQL。对于这些数据库,新的远程下推优化器会将查询直接发送到远程服务器,而不是通过网络拉取表数据。
CLI中的Quack
CLI还提供了设置Quack客户端-服务器对的快捷方式:新的-serve和-connect参数可以直接从命令行启动Quack服务器或连接到现有服务器:
#### CLI服务器
duckdb
-serve«memory» D
CREATE
TABLE
t
AS
SELECT
42
AS
x
;#### CLI客户端
duckdb
-connect«quack:localhost memory»
FROM
t
;
┌───────┐
│
x
│
│
int32
│
├───────┤
│
42
│
└───────┘性能
异步I/O
DuckDB 2.0为读取Parquet和CSV文件以及DuckDB原生存储格式引入了异步I/O。现在I/O层可以独立于查询处理层进行扩展,后台执行读取操作并保持多个请求同时进行。这意味着远程读取的并行性显著提高,对象存储(如s3://)上的查询速度也大幅提升。
我们已在专门的博客文章中详细介绍了该设计。在那篇文章中,我们展示了使用异步I/O前后查询过程中的网络吞吐量:
网络吞吐量。更高的利用率和更短的运行时间更优。
VARIANT类型成为一等公民
VARIANT类型存储半结构化、无模式的数据(如JSON),同时保持列式执行的性能。可以将其视为强化版的JSON:VARIANT列可以在每一行存储不同形状的数据,但与JSON不同的是,它不是文本格式。
DuckDB 1.5已引入VARIANT类型,但DuckDB 2.0对其进行了全面整合。DuckDB 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
),
variant_keys
(
payload
)
FROM
events
;┌───────────────────────┬───────────────────────┐
│ variant_type(payload) │ variant_keys(payload) │
│ varchar │ varchar[] │
├───────────────────────┼───────────────────────┤
│ OBJECT │ [user] │
│ OBJECT │ [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 解析器文档。新解析器设计为与旧解析器兼容,因此你不会察觉到切换。如果发现异常,请提交问题报告。在此期间,你可以通过运行 SET enable_legacy_parser = true; 使用 legacy_parser 扩展切换回旧解析器。
自 2018 年首次提交以来,旧解析器一直服务于 DuckDB。其语法由 YACC/Bison 生成,会生成 LALR(1) 解析器。随着 DuckSQL 的发展,即使是小的语法扩展也可能与现有规则冲突,导致移进/归约或归约/归约冲突。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 指纹,以便你与通过其他渠道发布的指纹进行比对。
该功能仍在开发中,未来版本可能会调整语法。更多详情请参见扩展仓库页面。
- Iceberg 扩展旨在实现功能完整的实现。为此,它添加了 REST 目录 ATTACH 选项,这些选项可以从表的命名空间推导出表的位置,或从目录返回的 metadata-location 文件中加载表元数据。其他选项用于控制 URL 前缀和命名空间分隔符。
- Delta 扩展可以通过 CREATE TABLE 语句(包括分区和 Delta 表属性)创建 Delta 表,支持时间旅行功能,并增强了 VARIANT 类型的处理能力。
SQL 特性
触发器
DuckDB 2.0 引入了触发器功能:支持 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
;┌─────────────┬─────────┬────────────┬─────────────┐
│ nationality │ sum_age │ num_people │ average_age │
│ varchar │ int32 │ int32 │ double │
├─────────────┼─────────┼────────────┼─────────────┤
│ NL │ 87 │ 3 │ 29.0 │
│ US │ 58 │ 1 │ 58.0 │
└─────────────┴─────────┴────────────┴─────────────┘触发器使我们更接近物化视图,这是我们在路线图“未来工作”部分长期存在的一个目标。
SQL 方言新增功能
一如既往,DuckDB 友好的 SQL 方言持续扩展。本次发布周期的一些亮点功能:
$变量简写允许在大多数表达式允许的位置使用$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 进程的复杂部署需要更多注意。存储格式最可能是问题的来源:DuckDB 2.0 之前的版本无法读取由 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次提交,由众多贡献者完成,包括小型功能、性能优化和易用性改进。完整的发布说明可在GitHub上找到。
感谢社区提供的详细问题报告和反馈,这些内容推动了本次发布。特别感谢所有贡献者!
##### 本文内容
- 作为服务器的DuckDB:CLI中的CONNECT命令
- 异步I/O性能 VARIANT类型成为一等公民 CLI中的代理模式
- 可扩展性:全新的SQL解析器 一次编写扩展并自行托管 核心扩展更新
- SQL功能:触发器 SQL方言新增功能
- 迁移到2.0版本
- 内部实现与弃用功能:编译器要求 无需ICU的时区、日历和排序功能 弃用功能
- 总结
## 最新文章
所有博客文章
### 在NVIDIA Vera上对DuckDB v2.0-dev进行基准测试
2026-10-16
3分钟
Gábor Szárnyas
### 从JSON字符串到类型化列
6分钟
### 冰川表内部结构是什么?
2026-10-13
12分钟