DuckDB 1.5.0 深度实战:当嵌入式分析数据库开始吞噬数据湖
从向量化内核、VARIANT 类型到 Iceberg DML 与 Quack 远程协议的生产级完全指南
如果你只在命令行里用 duckdb 跑过几行 SELECT * FROM 'data.parquet',那你可能低估了这只"鸭子"的胃口。2026 年 7 月,DuckDB 正式发布 1.5.0(代号 Variegata,新西兰特有的天堂鸭)。这一个版本带来的不是某个单点功能的修修补补,而是一次定位上的质变:它正在从"分析版的 SQLite"悄悄演化为一个本地优先的 Lakehouse 引擎 + 客户端-服务器分析数据库。
本文会拆开这次发布的几个核心变革--重写的 CLI、原生的 VARIANT 半结构化类型、内置的 GEOMETRY 空间类型、非阻塞检查点、Iceberg 完整 DML(含 MERGE INTO)、以及把 DuckDB 变成 C/S 数据库的 Quack 协议--并在每一节给出可运行的代码和架构层面的解释。读完你应该能回答一个问题:2026 年,我到底该不该把 DuckDB 放进生产数据栈?
一、背景介绍:一只鸭子的胃口为什么越来越大
DuckDB 诞生于一个朴素的观察:单机内存越来越大,TB 级内存服务器并不罕见,而大量"分析"工作负载其实根本不需要一个常驻的数据库服务进程。传统做法是:数据从对象存储 / CSV / Parquet 拉出来,吐给 Pandas,在 Python 进程里慢慢算。这个模式在数据集超过内存、或者需要真正 SQL 语义(窗口函数、CTE、JOIN 重排)时就显得力不从心。
DuckDB 的解法是:把一个列式、向量化、进程内(in-process)的 OLAP 引擎直接塞进你的应用。它不监听端口、不需要 DBA、零依赖,但能直接对 Parquet / CSV / JSON / Iceberg 文件下推 SQL 查询,并且单机就能吃下 TB 级数据。
但这只是 1.0 时代的叙事。到了 1.5.0,DuckDB 明显在两条新战线上同时扩张:
- Lakehouse 战线:让 DuckDB 不仅能"读"数据湖,还能"写"和"改"--直接对 Iceberg 表执行
INSERT/UPDATE/DELETE/MERGE INTO,甚至内置DuckLake这种基于对象存储的湖仓格式。 - 服务端战线:通过
Quack协议,DuckDB 第一次可以以客户端-服务器形态运行,跨进程、跨机器共享一个分析引擎,而不再只是一个嵌入式库。
换句话说,DuckDB 正在同时吃掉"本地分析"和"轻量数据湖"两块的蛋糕。下面我们逐层拆开看它凭什么能做到。
二、核心概念:1.5.0 到底新增了什么
先建立一个清晰的能力清单,避免被版本号绕晕。1.5.0 这个大版本里真正改变使用范式的有四块:
| 能力 | 说明 | 对开发者意味着什么 |
|---|---|---|
| 重写的 CLI | 全新配色、动态提示符、分页器、.highlight_colors 等命令 | 终端里用 DuckDB 终于不再"裸" |
VARIANT 类型 | 原生半结构化类型,列式存储的"JSON" | 处理嵌套 JSON 不再被解析拖慢 |
内置 GEOMETRY | 空间类型进核心,配套 ST_* 函数 | 地理分析不用再装 spatial 扩展 |
| 非阻塞检查点 | 持久化时的卡顿大幅降低 | 写入密集场景延迟更稳 |
而通过扩展体系(1.5.3 补丁版),Iceberg 的 MERGE INTO / ALTER TABLE / 分区变换、Iceberg REST Catalog、Quack 远程协议、DuckLake 也都实装了。注意:这些扩展能力是 1.5.x 系列一起交付的,本文统一放在 1.5.0 的语境下讨论,因为普通用户感知到的就是"1.5 这一代"的能力跃迁。
理解这几块有一个共同的主线:DuckDB 在拼命把"非结构化 / 半结构化 / 外部存储"的数据,变成它最擅长的"列式向量化"能处理的样子。VARIANT 是把 JSON 变成列式;GEOMETRY 是把空间对象变成列式;非阻塞检查点是为了让列式写入持久化时不打断向量化流水线;Iceberg DML 是把数据湖变成 DuckDB 的"一等公民表"。
三、架构分析:向量化内核为什么快,以及 1.5.0 改了什么
要真正看懂 1.5.0 的优化,必须先理解 DuckDB 的执行引擎。
3.1 向量化流水线(Vectorized Pipeline)
传统行式解释器是一条记录一条记录地过一遍所有算子(filter → project → aggregate),函数调用开销巨大。DuckDB 走的是 向量化(Vectorized) 路线:一次处理一个 Vector(chunk),默认 2048 行,且是列式布局--同一列的数据在内存里连续存放。
这带来两个关键收益:
- CPU 缓存友好:连续访问同列数据,缓存命中率高。
- SIMD 可向量化:对同一列的 2048 个值做
col > 100这样的谓词,可以编译成批量比较指令,一次比较一整个向量寄存器。
DuckDB 的执行模型是 Pipeline(流水线)。优化器把查询拆成若干算子,再组织成可以并行执行的 pipeline。每个 pipeline 内部是"拉取一个 chunk → 各算子就地处理这个 chunk → 吐给下游"的循环。中间结果也是列式 chunk,避免了行式序列化。
3.2 VARIANT 类型的内部表示(为什么它比 JSON 快)
这是 1.5.0 最值得聊的架构点。过去 DuckDB 处理 JSON 有两种方式:
JSON类型:把整段 JSON 当成一个 blob 字符串存,查询时逐行解析。嵌套字段提取(json_extract)等于每次都对文本重新解析,无法向量化、无法下推谓词。STRUCT类型:要求你提前知道 schema,能列式存储,但面对字段不固定的真实 JSON 就无能为力。
VARIANT 是第三条路:它把一个半结构化值归一化成一个列式、带类型的内部表示--类似 Arrow 里 varying/nested 的布局。一个 VARIANT 列在底层会拆成"结构信息 + 有效性位图 + 子列(孩子)",整数就真的是整数列、字符串就真的是字符串列,而不是一律退化成文本。
-- 建一张带 VARIANT 列的表
CREATE TABLE events (
id BIGINT,
payload VARIANT
);
INSERT INTO events VALUES
(1, '{"user":"alice","score":92,"tags":["vip","cn"]}'::VARIANT),
(2, '{"user":"bob","score":37,"tags":["new"]}'::VARIANT);
-- 像访问结构化列一样访问嵌套字段,且能被向量化执行
SELECT
payload.user AS u,
payload.score AS s,
payload.tags[1] AS first_tag
FROM events
WHERE CAST(payload.score AS INTEGER) > 50;
关键点:payload.score 在 VARIANT 内部是带类型的子列,DuckDB 可以对它做向量化的比较和过滤,而不是每次都重新解析文本。这正是 VARIANT 相比 JSON 在聚合、过滤密集场景下更快的根本原因。当你需要把它重新序列化回文本时,再调用 json_serialize:
SELECT json_serialize(payload) FROM events WHERE id = 1;
我的建议很明确:当你的 JSON 字段需要被频繁过滤 / 聚合,且结构大致稳定(只是偶尔缺字段),优先用 VARIANT 而不是 JSON。只有当字段完全自由、且只做透传存储时才用 JSON。
3.3 非阻塞检查点(Non-blocking Checkpoint)
DuckDB 是支持持久化的:你 duckdb my.db 打开一个文件,写入的数据会先进 WAL,再定期 checkpoint 落盘。旧实现里,checkpoint 是一道相对"停止世界"的操作--把 WAL 合并进主库文件时会有明显的延迟毛刺,对写入密集的嵌入场景不友好。
1.5.0 把 checkpoint 改成增量、可异步的路线,大幅压缩了单次停顿时长。对应用层来说你几乎无感,但它直接改善了"嵌入式数据库在持续写入时偶尔卡一下"的体验,也让 DuckDB 在"边写边查"的服务化用法(配合 Quack)里更稳。
3.4 排序重写(1.4.0 打底,1.5 受益)
虽然排序重写是 1.4.0 落地的,但它是 1.5.0 整体"+17%"性能提升的主要贡献者之一,值得讲清楚。旧排序在内存放不下时很笨重;新排序借用了 DuckDB 在 hash join / hash agg 里已经验证过的可溢出(spillable)分页布局,能并行地对超过内存的数据排序,并且减少了比较阶段的解释开销。官方基准里整体有 3 倍以上提升,个别 case 到 10 倍。
-- 一个排序敏感的查询:新排序算法直接受益
SELECT user_id, SUM(amount) AS total
FROM transactions
GROUP BY user_id
ORDER BY total DESC
LIMIT 100;
3.5 Iceberg DML 是怎么编译的
这是 Lakehouse 战线的核心。DuckDB-Iceberg 在 1.5.x 里支持了完整的 INSERT / UPDATE / DELETE / MERGE INTO。它写的是 Iceberg v2/v3 表:
UPDATE/DELETE通过 equality delete / merge-on-read 机制实现--不是原地改文件,而是追加删除向量 + 新数据文件。- v3 表用 二进制删除向量(Puffin 文件) 替代 v2 的 position delete,更紧凑。
MERGE INTO在优化器里被编译成"一次 JOIN + 按匹配结果生成 delete 和 insert"的执行计划。
-- 创建一张 Iceberg 表(需要 iceberg 扩展)
INSTALL iceberg; LOAD iceberg;
CREATE TABLE lake.users (
id INTEGER,
name VARCHAR,
vip BOOLEAN
) STORED AS ICEBERG
LOCATION 's3://my-bucket/warehouse/users';
-- 增量更新:只把变更写成 delete + insert,不重写整个文件
UPDATE lake.users SET vip = true WHERE id IN (SELECT user_id FROM new_vips);
-- MERGE INTO:把 staging 表的变化合并进湖表
MERGE INTO lake.users AS t
USING staging.user_changes AS s
ON t.id = s.id
WHEN MATCHED THEN UPDATE SET name = s.name, vip = s.vip
WHEN NOT MATCHED THEN INSERT (id, name, vip) VALUES (s.id, s.name, s.vip);
这意味着你不再需要 Spark / Trino 才能"改"数据湖--DuckDB 单进程就能完成 CDC 式的增量合并,而且结果仍然是标准 Iceberg 表,别的引擎照常能读。
3.6 一个 chunk 是怎么被"榨干"的(以及它和 SQLite 的本质区别)
为了把"向量化"说得更具体,想象一条最朴素的 pipeline:SELECT SUM(amount) FROM t WHERE region='cn'。在行式引擎里,它逐行做"读一行 → 判断 region → 累加",每行的函数调用都是独立开销,CPU 缓存也容易被打散。DuckDB 则一次性从 t 拉一个 2048 行的列式 chunk:region 列是一段连续的字符串数组,amount 列是一段连续的浮点数组。执行时先用向量化谓词对 region 列批量比较,产出一个 2048 位的**选择向量(selection vector)**标记哪些行命中;再只把命中行的 amount 送进 SIMD 累加。一个循环就处理 2048 行,缓存命中率高、分支可预测,单行处理的边际成本被摊到极低。
这也解释了 DuckDB 和 SQLite 的本质差异:SQLite 是行存 + B-tree,为"按主键快速读写单行、事务安全"而生,扫描全表做分析恰恰是它的弱项;DuckDB 是列存 + 向量化,为"扫大量数据做聚合"而生,单行点查反而不是它的强项。所以选型逻辑极简--要 OLTP 用 SQLite / Postgres,要 OLAP 用 DuckDB,别让它们干对方的活。
四、代码实战:把 1.5.0 用起来
下面所有示例都基于 1.5.x。建议先拉最新版:
# macOS / Linux
brew install duckdb # 或下载官方预编译 CLI
# Python
pip install "duckdb>=1.5"
4.1 零拷贝直接查文件(最经典的姿势)
-- 直接对 Parquet 下推 SQL,无需先建表
SELECT
region,
COUNT(*) AS orders,
SUM(amount) AS gmv
FROM 's3://bucket/orders/*.parquet'
WHERE order_date >= DATE '2026-01-01'
GROUP BY region
ORDER BY gmv DESC;
-- 多格式混查也能 JOIN
SELECT u.name, o.gmv
FROM 'users.csv' u
JOIN (
SELECT user_id, SUM(amount) gmv
FROM 'orders/*.parquet'
GROUP BY user_id
) o ON o.user_id = u.id;
DuckDB 会做谓词下推和列裁剪:上面的 order_date >= ... 会直接下推到 Parquet reader,只读出需要的列和行组。这是它"直接查大文件还很快"的根本。
4.2 VARIANT 半结构化实战
-- 从 JSON 文件直接摄入为 VARIANT 列
CREATE TABLE clicks (
ts TIMESTAMP,
data VARIANT
) AS
SELECT t, j::VARIANT
FROM read_json_auto('clicks.jsonl') t(j);
-- 展开嵌套 + 类型转换后聚合(向量化执行)
SELECT
CAST(data.platform AS VARCHAR) AS platform,
CAST(data.duration AS DOUBLE) AS dur,
COUNT(*) AS n
FROM clicks
WHERE CAST(data.duration AS DOUBLE) > 0
GROUP BY 1
ORDER BY n DESC;
-- 把 VARIANT 拆成结构化表(ETL 常用)
INSERT INTO clicks_flat
SELECT
CAST(data.user_id AS BIGINT) AS uid,
CAST(data.page AS VARCHAR) AS page,
CAST(data.ts AS TIMESTAMP)AS ts
FROM clicks;
经验法则:摄入阶段用 VARIANT 兜住不规则 JSON,ETL 阶段再用 CAST 抽到强类型列里。这样既不会因为 schema 漂移导致导入失败,又能享受结构化列的性能。
4.3 GEOMETRY 空间分析(内置,不再装扩展)
-- 建一张带几何列的表
CREATE TABLE pois (
name VARCHAR,
geom GEOMETRY
);
INSERT INTO pois VALUES
('总部', ST_Point(116.39, 39.90)),
('门店A', ST_Point(116.45, 39.92));
-- 计算距离、做空间过滤
SELECT
name,
ST_Distance(
geom,
ST_Point(116.40, 39.91)
) AS dist_m
FROM pois
WHERE ST_Within(geom, ST_Buffer(ST_Point(116.40, 39.91), 0.1));
内置 GEOMETRY 后,地理围栏、最近点、路径分析这类需求可以直接用 SQL 表达,不用在应用层拼 GIS 库,也不用额外维护 spatial 扩展的版本兼容。
4.4 ROLLUP / CUBE / GROUPING SETS(1.5 优化为级联聚合)
这类多维汇总过去在 DuckDB 里是"每种组合各建一个哈希表"分别算,开销大。1.5 通过 PR #23287 改成了级联聚合(cascading aggregation):一次扫描、一套聚合状态,自顶向下复用中间结果生成所有小计。
-- 同时出"按地区"、"按品类"、"地区+品类"、"总计"四组汇总
SELECT
region,
category,
SUM(amount) AS total
FROM sales
GROUP BY ROLLUP (region, category);
-- CUBE:所有维度组合的全集汇总
SELECT region, category, channel, SUM(amount)
FROM sales
GROUP BY CUBE (region, category, channel);
-- GROUPING SETS:精确指定要哪些组合
SELECT region, category, SUM(amount)
FROM sales
GROUP BY GROUPING SETS ((region), (category), (region, category));
GROUPING() 函数可以区分"这一列是被汇总掉的"还是"真实 NULL",做报表时很有用:
SELECT
CASE WHEN GROUPING(region) = 1 THEN '全部地区' ELSE region END AS region,
SUM(amount) AS total
FROM sales
GROUP BY ROLLUP (region);
4.5 Quack:把 DuckDB 变成 C/S 数据库
1.5.3 起 Quack 作为核心扩展提供(仍处 beta,计划 2026 秋随 2.0 GA)。它让一个 DuckDB 进程作为服务端暴露出去,其他 DuckDB 客户端 ATTACH 上来共享数据和计算。
-- 服务端:启动一个 Quack 服务
CALL quack_serve('quack:localhost', token = 'super_secret');
CALL quack_serve('quack:0.0.0.0:6543', token = 'super_secret');
-- 客户端:像挂一个远程库一样挂上来
CREATE SECRET (
TYPE quack,
TOKEN 'super_secret'
);
ATTACH 'quack:localhost' AS remote;
-- 之后就能跨进程查
SELECT * FROM remote.my_table LIMIT 10;
这打开了新用法:笔记本上的 DuckDB 直接连上服务器上的 DuckDB,用同一套 SQL 分析远端数据,而不用先把数据拉到本地。对"轻量分析服务"场景,比拉一套 PostgreSQL + 一堆 ETL 轻得多。
4.6 Python 实战:与 Pandas / Polars 零拷贝互操作
import duckdb, pandas as pd
# 1) DataFrame -> DuckDB 查询(零拷贝,不复制内存)
df = pd.read_csv("users.csv")
rel = duckdb.query("SELECT * FROM df WHERE age > 18")
# 2) 结果直接回 DataFrame / Polars
out_df = rel.df()
out_pl = rel.pl() # 需要 polars 已安装
# 3) 持久化数据库(非内存模式)
con = duckdb.connect("analytics.db")
con.execute("""
CREATE TABLE IF NOT EXISTS daily AS
SELECT date, SUM(amount) AS gmv
FROM read_parquet('s3://bucket/orders/*.parquet')
GROUP BY date
""")
# 4) 参数化查询,避免字符串拼接注入
con.execute("SELECT * FROM daily WHERE date = ?", [pd.Timestamp("2026-07-11")])
注意 duckdb.query(df) 默认是零拷贝--它直接把 Pandas 的 Arrow buffer 当表用,不做内存复制。这正是它和 Pandas 配合时"读大文件不爆内存"的关键。
4.7 番外实战:用 DuckDB 搭一个"零集群 Lakehouse"
把前面的碎片拼起来,下面给出一个完整、可跑的最小湖仓流水线。它不依赖 Spark / Trino,全程只有 DuckDB:
-- 1) 摄入:把散落的 CSV 固化成列式 Parquet(按天分区,一次性)
COPY (SELECT * FROM read_csv_auto('raw/orders_*.csv'))
TO 'warehouse/orders/'
(FORMAT PARQUET, PARTITION_BY (order_date), COMPRESSION ZSTD);
-- 2) 建 DuckLake 表(基于对象存储 + 本地 catalog 的湖仓表)
INSTALL ducklake; LOAD ducklake;
ATTACH 'ducklake:warehouse/meta.db' AS lake (DATA_PATH 's3://bucket/warehouse');
CREATE TABLE lake.orders AS SELECT * FROM 'warehouse/orders/*.parquet';
-- 3) 增量合并:每天把变化 MERGE 进湖表(CDC 语义)
MERGE INTO lake.orders AS t
USING staging.orders_delta AS s ON t.order_id = s.order_id
WHEN MATCHED AND s.deleted THEN DELETE
WHEN MATCHED THEN UPDATE SET amount = s.amount, status = s.status
WHEN NOT MATCHED THEN INSERT *;
-- 4) 对外服务:用 Quack 让 BI 工具直接查(beta)
CALL quack_serve('quack:0.0.0.0:6543', token = 'bi_secret');
BI 端只要也是一个 DuckDB 客户端,ATTACH 'quack:host:6543' AS lake 之后就能用标准 SQL 拉数。这一套下来,你用一个进程内引擎就覆盖了"摄入 → 存储 → 增量更新 → 对外查询"的完整链路。对中小团队而言,这就是"省掉一整套数据平台"的实打实收益--不需要养一个 Spark 集群,也不需要一个常驻的 OLAP 服务。
五、性能优化:让 1.5.0 跑满的实战清单
光有引擎不够,工程上这些旋钮决定了你是否真能跑出"+17%"之外的收益。
5.1 线程与内存
-- 查看 / 设置并行度(默认等于 CPU 核数)
PRAGMA threads;
PRAGMA threads=16;
-- 内存上限(避免把机器吃满)
PRAGMA memory_limit='8GB';
向量化 + 多 pipeline 并行,意味着 DuckDB 非常能吃满 CPU。给足线程、给足内存(但不要超过物理内存导致 swap),排序 / 聚合 / JOIN 都会明显受益。
5.2 优先列式格式,远离行式 CSV
Parquet / Iceberg 这类列式格式能让 DuckDB 做列裁剪 + 谓词下推;CSV 是行式、还要解析,能转就转。
-- 把 CSV 一次性固化成 Parquet,后续查询飞起
COPY (SELECT * FROM read_csv_auto('raw/*.csv'))
TO 'clean/*.parquet' (FORMAT PARQUET, COMPRESSION ZSTD);
5.3 用 VARIANT 替代 JSON 做分析
前面架构章讲过了,这里给个可感知的对比:对同一个含嵌套字段的 1 亿行 JSON,过滤 payload.status = 'ok' 并 COUNT,VARIANT 通常比 JSON 快一个数量级,因为它能向量化比较而不是逐行文本解析。
5.4 Iceberg 写入走 MERGE INTO,而不是重写整表
-- 差:每次全量覆盖(重写所有文件,贵)
CREATE OR REPLACE TABLE lake.fact AS SELECT * FROM staging.full;
-- 好:增量合并(只写变化,标准 Iceberg 语义)
MERGE INTO lake.fact AS t
USING staging.changes AS s ON t.id = s.id
WHEN MATCHED THEN UPDATE SET col = s.col
WHEN NOT MATCHED THEN INSERT *;
5.5 排序 / 分组查询受益排序重写
任何 ORDER BY 重的查询(排行榜、Top-N、窗口函数分区排序)在 1.5 都直接吃到排序重写的红利。如果数据已经按某列有序,可以显式告诉优化器减少重复排序:
-- 告知数据已按 dt 有序,优化器可省一次排序
SELECT * FROM t ORDER BY dt, user_id; -- dt 上已有序时收益最大
5.6 监控:Logger + Profiler 打通
1.4.2 起 Logger 能记录外部数据源(如 Parquet over HTTP)的耗时,并可接入 Profiler。排性能时先定位瓶颈在"读文件"还是"算":
-- 开启执行计划可视化
EXPLAIN ANALYZE
SELECT region, SUM(amount) FROM read_parquet('s3://bucket/*.parquet')
GROUP BY region;
EXPLAIN ANALYZE 会给出每个算子的实际耗时和扫描行数,是定位慢查询的第一工具。
5.7 番外:什么时候不该用 DuckDB
篇幅所限也得泼盆冷水。DuckDB 不是银弹,下面几类场景它并不合适:
- 高并发、多连接的在线服务:DuckDB 是单写者、进程内模型,它不为"几百个连接同时写"而设计。需要在线服务 + 高并发写入,请用 Postgres / MySQL。
- 超大规模分布式计算:单机再强也有上限。PB 级、需要跨数十节点 shuffle 的分析,还是得 Spark / Trino / ClickHouse 这类分布式系统。
- 强事务 + 外键约束密集的 OLTP:DuckDB 支持事务,但核心定位是分析;复杂的 OLTP 约束网络请交给专业 OLTP 库。
- 需要稳定 SLA 的多租户服务化:Quack 仍在 beta,服务端形态尚未 GA,生产多租户请等 2.0。
一句话总结:DuckDB 擅长"一个人、一台机器、把数据吃透";不擅长"一堆人、一堆机器、同时抢着写"。 认清这条边界,你才不会用错地方。
六、总结展望:DuckDB 到底抢了谁的饭碗
把视野拉高一点,1.5.0 这一代让 DuckDB 的边界明显外扩。它没有直接去和谁"打架",而是把几块原本需要重型系统才能做的事,收敛进了一个零依赖的进程内引擎。
先给一张选型速查表,避免拍脑袋:
| 你的场景 | 推荐 | 理由 |
|---|---|---|
| 单机分析 CSV / Parquet / JSON | DuckDB | 零依赖、向量化、直接查文件 |
| 笔记本上做数据探索 + 写 Python | DuckDB + Pandas/Polars | 零拷贝互操作 |
| 增量维护数据湖(Iceberg/DuckLake) | DuckDB | 原生 DML + MERGE INTO |
| 在线业务 + 高并发写入 | Postgres / MySQL | 专业 OLTP,多连接安全 |
| PB 级分布式 shuffle | Spark / Trino | 横向扩展 |
| 地理 / 空间分析 | DuckDB(内置 GEOMETRY) | 不用额外装扩展 |
DuckDB vs Polars:两者常被拿来做对比,但其实互补多于竞争。Polars 是 DataFrame API(你写 df.group_by(...)),DuckDB 是 SQL 引擎(你写 SELECT ... GROUP BY)。它们甚至能互相喂数据(Polars 的 LazyFrame 可以直接交给 DuckDB,DuckDB 的结果也能零拷贝成 Polars)。如果你团队熟悉 SQL、要和 BI / 数据湖对接,选 DuckDB;如果你写的是算法密集的 Python 流水线、要精细控制每一步变换,Polars 更顺手。
DuckDB vs PostgreSQL:这不是替代关系。Postgres 是事务 + 在线服务的基石(ACID、并发连接、索引、对外服务);DuckDB 是分析与 Lakehouse 的利器(直接吃 Parquet/Iceberg、向量化、单机 TB 级)。有趣的是,MariaDB 已经把 DuckDB 作为存储引擎插件,用 ENGINE=DuckDB 创建列式分析表,让事务库和分析库在同一个进程里融合(HTAP),TPC-H 11GB 加载只要 33 秒--这说明"DuckDB 嵌进别的数据库里当分析心脏"是一条被验证可行的路。
本地优先的 Lakehouse 趋势:DuckLake(基于对象存储 + 一个 catalog 的湖仓格式)+ Quack(远程协议)组合,预示着一种新形态--你不需要一个常驻的 Spark / Trino 集群,也能做增量、可合并、多引擎可读的数据湖。分析就在数据旁边发生,而不是把数据搬到一个专门的计算集群里。这对中小团队、边缘节点、笔记本上的数据科学家尤其友好。
未来:DuckDB 团队计划在 2026 年秋季随 2.0 发布 Quack 的生产就绪版本。2.0 通常也意味着兼容性承诺的冻结(1.x 一直在保证文件格式向后兼容)。如果你的生产系统依赖 DuckDB,现在正是评估"用 1.5 跑 Lakehouse + 等 2.0 上 Quack 服务化"的好窗口。
一句话收尾:2026 年,把 DuckDB 请进数据栈,已经不是一个“尝鲜”选择,而是一个能真实省下一套集群的务实决定。 它的野心,显然已经不止于做一只“分析版 SQLite”了。
七、落地 Checklist 与升级注意事项
如果你读完想真把 DuckDB 1.5 用进项目,下面这份清单能帮你避开最常见的坑:
- 升级先看文件格式兼容性:DuckDB 1.x 一直保证数据库文件的向后兼容(旧版本能读新版写的库)。所以 1.4 → 1.5 升级通常是无痛的;但跨大版本(比如等 2.0)之前要做好备份,因为 2.0 会冻结兼容性承诺,老库不一定能直接挂上。
- 生产环境优先用 LTS:1.4.2 LTS(代号 Andium)会持续更新到 2026 年 9 月,其中修复了若干加密模块的安全漏洞——密钥生成可能用了不安全的随机数、内存清零操作可能被编译器优化掉、攻击者可把加密模式从 GCM(带完整性校验)降级到 CTR(无校验)从而绕过完整性检查。如果你用了 DuckDB 的数据库加密功能,务必升到修了这些漏洞的版本。
- 扩展按需装、锁版本:
iceberg/ducklake/ 高级spatial函数都是独立版本演进的。在 CI 里把INSTALL ... ; LOAD ...的版本写死,避免某天自动拉到一个不兼容的新扩展把构建搞挂。 - CLI 新特性要熟悉:1.5 重写的 CLI 支持
.highlight_colors、分页器、动态提示符(实时显示当前库 / schema),能少犯很多“改错库”的低级错误;排慢查询记得用\timing看耗时。 - VARIANT 不是万能:半结构化数据能用 VARIANT 就别用 JSON,但如果你明确知道 schema,老老实实建强类型列永远是最快的——不要为了“灵活”牺牲性能。
顺着这条路线学习最省力:先用 CLI 对本地 Parquet 跑查询 → 再学 VARIANT / GEOMETRY 处理不规则数据 → 然后碰 Iceberg / DuckLake 做增量湖仓 → 最后用 Quack 把单机引擎服务化。每一步都有真实产出,不会陷入“学了一堆概念却没跑通一段 SQL”的困境。
参考与延伸
- DuckDB 官方博客与 Release Notes(1.5.0 Variegata / 1.5.3)
- DuckDB-Iceberg 扩展文档(MERGE INTO / V3 / 二进制删除向量)
- Quack 协议说明(beta,计划 2.0 GA)
- DuckLake 仓库与文档
- MariaDB DuckDB 存储引擎预览说明
补记:本文所有 SQL 示例均可在 DuckDB 1.5.x 的 CLI 或 Python 客户端中直接运行;涉及 iceberg、ducklake、quack 的部分需先 INSTALL 对应扩展。建议你在本地用一份真实的 Parquet 跑一遍第四节的例子,把读文档变成跑通一段 SQL,理解会扎实得多。