A Preview of DuckDB v2.0

A Preview of DuckDB v2.0

DuckDB v2.0 预览

Mark Raasveldt and Hannes Mühleisen | 2026-08-17 | 15 min

TL;DR: DuckDB v2.0 is coming this fall. In this post, we preview its headline features: DuckDB as a server, triggers, the VARIANT type, asynchronous I/O, a new SQL parser, a new storage format, and much more. 简而言之: DuckDB v2.0 将于今年秋季发布。在这篇文章中,我们将预览其核心功能:DuckDB 服务器模式、触发器、VARIANT 类型、异步 I/O、全新的 SQL 解析器、全新的存储格式等等。

DuckDB v2.0 will be named “Cyanoptera” after the cinnamon teal (Anas cyanoptera), a strikingly reddish-brown duck found in the western Americas. A major version bump is not something we do lightly, and it is not just ceremony: v2.0 ships a new SQL parser, a new default storage format, a reworked C API, and a small number of carefully chosen breaking changes. But above all, it is a feature release, built from over 10,000 commits since we released v1.5 in March. DuckDB v2.0 将被命名为“Cyanoptera”,取名自肉桂鸭(Anas cyanoptera),这是一种在美洲西部发现的、有着引人注目的红褐色羽毛的鸭子。大版本更新并非儿戏,也不仅仅是仪式感:v2.0 带来了全新的 SQL 解析器、全新的默认存储格式、重构的 C API 以及少量经过深思熟虑的破坏性变更。但最重要的是,这是一个功能性版本,自我们在三月份发布 v1.5 以来,它包含了超过 10,000 次提交。

Where last year was the year of the lakehouse, this release kicks off the year of DuckDB as a server. We previewed many of these features in the “State of the Duck” talk at DuckCon #7, if you prefer to watch instead of read. DuckDB is moving rather quickly, and we can only cover a small fraction of the changes here. Condensing all new features down to a shortlist is always a fight over what gets in, and yes, we know that what follows is technically a listicle (Ten Things Coming to DuckDB v2.0, Number Eight Will Shock You). We are not proud of the format, but it works, so here it is, starting with the SQL-level features and working down into the engine. 如果说去年是“湖仓一体”之年,那么本次发布则开启了“DuckDB 作为服务器”的元年。如果您更喜欢观看视频而非阅读,我们在 DuckCon #7 的“State of the Duck”演讲中预览了其中的许多功能。DuckDB 的发展非常迅速,我们在此只能涵盖其中一小部分变更。将所有新功能浓缩成一份简短列表总是一场关于“取舍”的博弈,是的,我们知道接下来的内容从技术上讲是一篇列表文(《DuckDB v2.0 即将到来的十件事,第八件会让你震惊》)。我们并不以这种格式为荣,但它确实有效,所以让我们开始吧,从 SQL 层面功能一直深入到引擎底层。

1. DuckDB as a Server: Quack and CONNECT

1. DuckDB 作为服务器:Quack 与 CONNECT

DuckDB has been an in-process database since day one. But people have asked us – very persistently – for a client/server mode, and we have finally caved. The quack extension implements DuckDB’s native protocol for talking to other DuckDBs. It was released as a preview shortly before DuckCon #7, graduates to stable in v2.0, and it is a big part of where DuckDB is headed: any DuckDB process can serve its databases over the network, and any other DuckDB can attach to it and route queries there using the new CONNECT statement. DuckDB 自诞生之日起就是一款进程内数据库。但人们一直非常执着地要求我们提供客户端/服务器模式,我们最终妥协了。Quack 扩展实现了 DuckDB 与其他 DuckDB 实例通信的本地协议。它在 DuckCon #7 前夕作为预览版发布,在 v2.0 中正式转为稳定版,这是 DuckDB 未来发展的重要组成部分:任何 DuckDB 进程都可以通过网络提供数据库服务,而其他任何 DuckDB 实例都可以通过新的 CONNECT 语句连接到它并路由查询。

For example: 例如:

-- DuckDB server
CALL quack_serve(token = 'my_token');

-- quack: DuckDB client
ATTACH 'quack:server.example.com' AS qk (TOKEN 'my_token');
CONNECT qk;
SELECT count(*) FROM events; -- executes on the server, results stream back
DISCONNECT;

CONNECT is the successor to the remote.query($$…$$) workaround we showed when Quack was first revealed – we looked at that syntax and said: no, this cannot be it. And CONNECT is not limited to Quack: it points your session at any remote database that supports it, and the new remote pushdown optimizer (#22914) ships SQL directly to PostgreSQL and MySQL instead of pulling tables over the wire: CONNECT 是我们最初展示 Quack 时所使用的 remote.query($$...$$) 变通方案的继任者——我们审视了那种语法并认为:不,这不应该是最终形态。而且 CONNECT 不仅限于 Quack:它能将您的会话指向任何支持它的远程数据库,并且新的远程下推优化器(#22914)会将 SQL 直接发送到 PostgreSQL 和 MySQL,而不是通过网络拉取整个表:

CONNECT 'postgres://localhost/mydb';
SELECT count(*) FROM orders; -- runs on the PostgreSQL server
DISCONNECT;

If you have worked with analytical systems in the past, you may assume that DuckDB cannot handle transactional workloads. But DuckDB has been built as a transactional, multi-connection database with full MVCC and transaction isolation since day one. Most users just never needed that in a single-user scenario. It turns out DuckDB handles transactions well: it’s fast enough to compete with general-purpose databases like PostgreSQL on quite a few workloads, and the client/server pattern finally lets that machinery shine in multi-tenant, long-running deployments. 如果您过去曾使用过分析系统,您可能会认为 DuckDB 无法处理事务性工作负载。但 DuckDB 自第一天起就被构建为支持完整 MVCC 和事务隔离的事务性、多连接数据库。大多数用户在单用户场景下从未需要这些功能。事实证明,DuckDB 处理事务的能力非常出色:在不少工作负载下,它的速度足以与 PostgreSQL 等通用数据库竞争,而客户端/服务器模式终于让这一机制在多租户、长期运行的部署中大放异彩。

Running DuckDB long-term also comes with new challenges, which is why v2.0 pushes on better metrics, logs, and observability (see, e.g., the metrics layer rework in #22799) that let you look at a DuckDB instance and see what it is actually doing. People even built standalone clients for the Quack protocol within weeks of the preview. We thought we were extending DuckDB to talk to other DuckDBs; the world said no, no, no, and built their own clients. Who would have thought. 长期运行 DuckDB 也带来了新的挑战,这就是为什么 v2.0 推动了更好的指标、日志和可观测性(例如,参见 #22799 中的指标层重构),让您可以查看 DuckDB 实例并了解它正在执行的操作。甚至在预览版发布后的几周内,人们就为 Quack 协议构建了独立的客户端。我们原以为只是在扩展 DuckDB 以便与其他 DuckDB 通信;但世界却说“不,不,不”,并构建了他们自己的客户端。谁能想到呢。

2. VARIANT Becomes a First-Class Citizen

2. VARIANT 成为一等公民

The VARIANT type shipped in DuckDB v1.5, and the way to think about it is JSON on steroids. Basically, imagine if JSON were fast. Like JSON, a VARIANT column can store differently-shaped data in every row. Unlike JSON, it is not a text format: DuckDB automatically detects the common structure hidden in your semi-structured data and “shreds” it, so it compresses well in storage and executes fast in queries, all without you ever declaring a schema. VARIANT 类型在 DuckDB v1.5 中发布,你可以把它想象成“强化版 JSON”。简单来说,想象一下如果 JSON 运行得很快会怎样。与 JSON 一样,VARIANT 列可以在每一行存储不同形状的数据。但与 JSON 不同的是,它不是文本格式:DuckDB 会自动检测隐藏在半结构化数据中的公共结构并将其“拆解”(shred),因此它在存储中压缩效果极佳,在查询中执行速度极快,且无需您声明任何模式。

This makes VARIANT a natural fit for real-time log ingestion, where streams of JSON-ish records share structure but evolve over time. In v2.0, this pipeline works end to end: shredded execution straight from storage (#20912), extraction pushdown into scans (#22478), shredded VARIANT reading and writing for Parquet, and a family of variant_* functions: 这使得 VARIANT 成为实时日志摄取的天然选择,因为 JSON 风格的记录流通常共享结构但会随时间演变。在 v2.0 中,这一流水线实现了端到端的打通:直接从存储进行拆解执行(#20912)、提取下推到扫描阶段(#22478)、支持 Parquet 的拆解 VARIANT 读写,以及一系列 variant_* 函数:

CREATE TABLE events (payload VARIANT);
INSERT INTO events VALUES ('{"user": {"id": 42, "tags": ["a", "b"]}}'::JSON::VARIANT);
SELECT variant_type(payload), variant_keys(payload) FROM events;
SELECT * FROM events WHERE variant_contains(payload, {'user': {'id': 42}}::VARIANT);

Longer term, likely soon after v2.0 (but don’t hold us to it), we plan to back the regular JSON type with VARIANT, so existing JSON workloads get all of these benefits without changing a single query. 从长远来看,很可能在 v2.0 发布后不久(但请不要以此为准),我们计划用 VARIANT 来支撑常规的 JSON 类型,这样现有的 JSON 工作负载无需更改任何查询即可获得所有这些优势。

3. Triggers

3. 触发器

Triggers have been a long-standing feature request, and DuckDB v2.0 delivers them in full: BEFORE and AFTER triggers, FOR EACH ROW and FOR EACH STATEMENT, transition tables via REFERENCING OLD/NEW TABLE, multiple triggers per event, RETURNING on triggered tables, and DROP TRIGGER. The classic use case is audit tables: something happens in the system, and a trigger records what changed. 触发器是一项长期以来的功能需求,DuckDB v2.0 完整地实现了它们:包括 BEFOREAFTER 触发器、FOR EACH ROWFOR EACH STATEMENT、通过 REFERENCING OLD/NEW TABLE 实现的过渡表、每个事件支持多个触发器、触发器表上的 RETURNING 子句以及 DROP TRIGGER。经典的用例是审计表:当系统中发生某些操作时,触发器会记录变更内容。

For example: 例如:

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      | 100
-- 2  | 20      | 200

Triggers fit naturally with… 触发器与……自然契合。