
DuckDB v2.0 抢先看:10 个新特性逐个拆解,含完整 SQL 示例
DuckDB 官方发布了 v2.0 预览:服务端模式、VARIANT 类型、触发器、全新 SQL 解析器、存储格式升级——10 个新特性带代码示例逐个讲清楚。
原文来源:DuckDB 官方博客 — DuckDB v2.0(代号 Cyanoptera)预览:从 SQL 层特性到引擎底层,10 个新特性逐个拆解,附带完整代码示例。
这次升级有多大
DuckDB v2.0 将于今年秋季正式发布,代号 "Cyanoptera"(取自美洲的一种红褐色鸭子)。这是一次真正的大版本升级:新 SQL 解析器、新默认存储格式、重构的 C API,以及少量经过精心挑选的破坏性变更。从 3 月发布 v1.5 以来,v2.0 积累了超过 10,000 个提交。
官方博客的说法是:去年是 lakehouse 之年,这次发布开启了"DuckDB 作为服务器"的一年。下面按从 SQL 层到引擎底的顺序,逐个拆解 10 个新特性。
—— 广告 ——
1. DuckDB 作为服务器:Quack 与 CONNECT
DuckDB 从第一天起就是进程内数据库,但用户一直在要求客户端/服务器模式。v2.0 通过 quack 扩展实现:任何 DuckDB 进程都可以通过网络提供数据库服务,其他 DuckDB 用新的 CONNECT 语句挂载并路由查询。
服务端启动:
CALL quack_serve(token = 'my_token');客户端连接:
ATTACH 'quack:server.example.com' AS qk (TOKEN 'my_token');
CONNECT qk;
SELECT count(*) FROM events; -- 在服务器上执行,结果流式返回
DISCONNECT;CONNECT 不限于 Quack——新的远程下推优化器可以把 SQL 直接推送到 PostgreSQL 和 MySQL 执行,而不是把整表拉过来:
CONNECT 'postgres://localhost/mydb';
SELECT count(*) FROM orders; -- 在 PostgreSQL 服务器上执行
DISCONNECT;DuckDB 其实从第一天起就是带完整 MVCC 和事务隔离的多连接事务数据库,只是单用户场景下很少用到。客户端/服务器模式终于让这套机制在多租户、长期运行的部署中发挥作用,v2.0 也因此在指标、日志和可观测性上做了大量工作(如指标层重构)。
2. VARIANT 成为一等公民
VARIANT 类型在 v1.5 中引入,可以理解为"打了激素的 JSON":每一行的数据形状都可以不同,但不像 JSON 那样以文本存储——DuckDB 会自动检测半结构化数据里的共同结构并"切碎"(shred)存储,压缩好、查询快,还不用声明 schema。非常适合实时日志摄入这类"结构相似但不断演化"的 JSON 流。
v2.0 让这条管线端到端跑通:存储直接执行切碎数据、扫描时提取下推、Parquet 的切碎读写,以及一整套 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);更长远的计划:v2.0 之后(不保证时间),普通 JSON 类型也会用 VARIANT 做底层,现有 JSON 工作负载一行查询都不用改就能享受全部收益。
3. 触发器
触发器是呼声极高的长期功能请求,v2.0 一次性给全:BEFORE 和 AFTER 触发器、FOR EACH ROW 和 FOR EACH STATEMENT、REFERENCING OLD/NEW TABLE 过渡表、每个事件多个触发器、RETURNING,以及 DROP TRIGGER。
经典用例是审计表:
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 | 2004. SQL 方言扩展
这一版新增了不少 SQL 语法,挑几个亮点:
NEAREST 连接(#24137):top-k 相似度搜索直接变成连接子句,对向量/embedding 工作负载很友好:
SELECT q.user_id, t.product_id
FROM users q
INNER JOIN products t APPROX NEAREST 2
BY SIMILARITY array_cosine_similarity(q.embedding, t.embedding);CTE 内 DML(#21634 等):INSERT、UPDATE、DELETE、COPY 可以作为管线步骤使用:
WITH moved AS MATERIALIZED (
DELETE FROM staging RETURNING *
)
INSERT INTO archive SELECT * FROM moved;嵌套 schema(#23492):schema 里可以有 schema:
CREATE SCHEMA finance;
CREATE SCHEMA finance.reports;
CREATE TABLE finance.reports.q3 (revenue DECIMAL);变量语法(#21194):$x 可以在任何表达式位置使用,不再需要 getvariable(...):
SET VARIABLE threshold = 100;
SELECT * FROM orders WHERE amount > $threshold;JSON 修改函数(#23786):json_set、json_insert、json_replace、json_remove 终于可以原地修改 JSON 文档:
SELECT json_set('{"a":1}', '$.b', '2');
-- {"a":1,"b":2}带 USING KEY 聚合的递归 CTE(#19481):纯 SQL 就能写迭代算法,背后是重写的递归 CTE 引擎:
WITH RECURSIVE tbl(a, b) USING KEY (a, avg(b)) AS (
SELECT 1, 5
UNION
SELECT a, b - 1 FROM tbl WHERE b > 0
)
TABLE tbl;另外还有标准化的 FETCH FIRST 2 ROWS ONLY、OVERLAY()、GROUP BY 里的 UNNEST、多匹配行的 MERGE/UPDATE ... FROM 语义等。
5. 异步 I/O
与对象存储(S3 等)交互是 DuckDB 的核心场景。以前是同步访问,限制了速度;v2.0 在整个引擎中引入异步 I/O,I/O 层与查询处理层独立扩展,网络存储上的远程读取并行度大幅提升。Parquet 先行(#23662),CSV(#23961)和 DuckDB 自己的文件格式(#24654)随后跟上,还支持异步 Parquet 写入和新的 MMAP、DIRECT_IO 模式。本地存储也有小幅提升,但网络存储才是收益大头。
6. 全面提速
- 递归 CTE 引擎重写(#22211)。官方给了一个笔记本可跑的微基准:百万条边的图上做单源可达性查询,v1.5.4 用时 4.90 秒,v2.0 预览版 0.12 秒——约 40 倍提速。
- 部分聚合下推到连接之下(#22572),冗余聚合复用(#24543)。
- 聚合超出内存时落盘(#24499)。
- 行组剪枝大幅扩展:min-max 索引(zone maps)和 Parquet Bloom filter 现在能跳过 structs、lists、decimals、UUID、
IN过滤,甚至函数谓词:
SELECT * FROM logs WHERE contains(message, 'ERROR');
SELECT * FROM t WHERE substr(code, 1, 3) = 'NL-';
SELECT * FROM 'data/*.parquet' WHERE id IN (1, 5, 9);- 分区感知查询规划(#22336):对 DuckLake、Iceberg、Hive 分区 Parquet 等 lakehouse 格式,规划器会利用现有分区跳过大部分数据。
7. 存储格式 v2.0
默认存储格式升到 v2.0.0(#22875),头号变化是 buffer-managed ART 索引(#21458):索引不再常驻内存,大索引表秒开,索引按需分页载入。列元数据改为惰性加载(#22333),宽表打开更快;DICT_FSST 字符串压缩默认开启(#23733),删除记录存储更紧凑,读时校验更强。一句话:大索引、宽表的数据库打开更快、内存占用更少。
8. 全新 SQL 解析器
DuckDB 一直用派生自 PostgreSQL 的解析器,v2.0 换成了自研的现代 PEG 解析器(#22194)。意义在于:
- 扩展可以挂钩语法本身——未来会出现暴露全新 SQL 语法的扩展
- 更精确的报错信息和源码位置
- 第一个方言兼容模式:
SET dialect_compatibility_mode = 'spark';官方说解析器设计上兼容旧版,正常使用应该感知不到变化;如果你感知到了,请去提 issue。
9. 去掉 ICU:时区、日历、排序规则原生实现
DuckDB 的时区、日历和排序规则一直由 ICU 库提供,但只用了它的一小片功能,却要在每个发行版里都带着它。v2.0 里 ICU 被彻底移除,icu 扩展自己实现了这些能力(#24463),时区数据直接取自 IANA 数据库并压缩到约 45 kB:
SELECT '2026-08-14 12:00:00'::TIMESTAMPTZ AT TIME ZONE 'Europe/Paris';
SELECT * FROM names ORDER BY name COLLATE de;体量更小、更新更容易,还更快:MacBook 上把 2500 万行时间戳转时区,v1.5.4 用时 0.24 秒,v2.0 原生实现 0.11 秒(2.2 倍);500 万行德文排序过滤,0.15 秒降到 0.06 秒(2.6 倍)。
10. 扩展只写一次,自己托管
以前大多数扩展(包括官方扩展)都构建在不稳定的 C++ API 上,每次 DuckDB 发版都要重新适配构建,社区扩展也可能因为作者不跟进而悄悄消失。v2.0 把稳定的 C API 扩展到"一次编写、一次构建、一次发布、永久可用"的程度。
为此 C API 改为从声明式、带版本号的规范自动生成(#24135):duckdb.h、duckdb_extension.h 和扩展 ABI 的每个函数都在 api_spec/ 目录的 YAML 里描述,CI 校验头文件与规范一致,API 和 ABI 再也不会漂移。还带来统一符号版本管理、自定义分配处理器、C API 扩展静态链接进应用等能力。官方给了一个完整的最小扩展示例——单个文件注册一个向量化标量函数:
#include "duckdb_extension.h"
DUCKDB_EXTENSION_EXTERN
static void AddNumbers(duckdb_function_info info, duckdb_data_chunk input, duckdb_vector output) {
idx_t count = duckdb_data_chunk_get_size(input);
int64_t *a = (int64_t *) duckdb_vector_get_data(duckdb_data_chunk_get_vector(input, 0));
int64_t *b = (int64_t *) duckdb_vector_get_data(duckdb_data_chunk_get_vector(input, 1));
int64_t *result = (int64_t *) duckdb_vector_get_data(output);
for (idx_t row = 0; row < count; row++) {
result[row] = a[row] + b[row];
}
}
DUCKDB_EXTENSION_ENTRYPOINT(duckdb_connection con,
duckdb_extension_info info,
duckdb_extension_access *access) {
duckdb_scalar_function f = duckdb_create_scalar_function();
duckdb_scalar_function_set_name(f, "add_numbers");
duckdb_logical_type bigint = duckdb_create_logical_type(DUCKDB_TYPE_BIGINT);
duckdb_scalar_function_add_parameter(f, bigint);
duckdb_scalar_function_add_parameter(f, bigint);
duckdb_scalar_function_set_return_type(f, bigint);
duckdb_destroy_logical_type(&bigint);
duckdb_scalar_function_set_function(f, AddNumbers);
duckdb_register_scalar_function(con, f);
duckdb_destroy_scalar_function(&f);
return true;
}LOAD add_numbers;
SELECT add_numbers(40, 2);额外消息:DuckDB Foundation 咨询委员会
秋季起,DuckDB Foundation 会增设利益相关者咨询委员会,为 DuckDB、DuckLake 和 Quack 的开发路线图提供意见,让关键利益相关方对项目方向有发言权。
升级提醒
v2.0 也包含少量破坏性变更:新的默认存储格式、lambda 语法过渡完成等,会在正式发布公告中详细说明。现在就想尝鲜的话,预览版构建已经包含大部分功能。
© 2026 四月
原文链接:https://www.aprilzz.com/tutorials/duckdb-2-0-preview
相关文章
DuckDB 为什么这么快?深入解析其内部架构(上篇)
从查询解析到存储层,一文看懂 DuckDB 的六大性能设计选择:进程内执行、列式存储、向量化执行、Morsel 驱动并行等
Qwen3.8-27B 本地部署实操:从 GGUF 到 vLLM,一块 24GB 显卡就能跑
手把手把 Qwen3.8-27B 跑起来:硬件要求、量化档位怎么选、Ollama / llama.cpp / vLLM 三条路线完整命令,以及 262K 上下文的坑。
SQLite 藏了 16 年的 WAL-Reset 损坏 bug:检查你的版本,三步升级修复
SQLite 3.7.0 到 3.51.2 存在 WAL-Reset 竞态 bug,特定条件下导致数据库静默损坏,Tailscale 半年踩坑 19 次。本文教你检查 SQLite 版本、确认是否受影响,并安全升级到修复版本。