DuckDB 深度实战:把「分析引擎」塞进应用进程里——向量化执行、列式存储与直查 PB 级 Parquet 湖仓的完整指南(2026)
关键词:DuckDB、in-process OLAP、向量化执行、列式存储、Parquet、数据湖仓、ETL、Arrow、pg_duckdb
有人说 DuckDB 是「分析界的 SQLite」。这句话只说对了一半:它确实像 SQLite 一样零依赖、嵌入式、文件即数据库;但它干的事恰恰和 SQLite 相反——SQLite 为「一次查一行」的点查而生,DuckDB 为「一次扫一亿行」的分析而生。本文带你从工程视角彻底搞懂它为什么快、怎么用、以及它到底该放在你架构的哪一层。
一、背景介绍:为什么我们需要一个「嵌入式分析数据库」
1.1 数据的三种体量
做工程的我们都知道,数据规模大致可以切成三档,每一档对应一套完全不同的技术选型:
- 内存放得下(百 MB 以内):用 Python 的
pandas、R 的data.frame就够了,循环也跑得动。 - 磁盘放得下、单机放得下(GB ~ 数 TB):这是绝大多数公司 80% 的真实业务数据所处的区间——一张几年的订单表、一份完整的用户行为日志、一个季度的风控特征。
- 单机放不下(PB 级、需要横向扩展):Hadoop/Spark、ClickHouse 集群、Snowflake、StarRocks 这类分布式系统登场。
过去十年的基础设施投资几乎全砸在第三档(分布式)和第一档(内存 DataFrame)上,而中间那一档长期是个尴尬的真空地带:用 pandas 吧,内存爆了;上 Spark 吧,为了分析 200 GB 数据专门养一个 YARN 集群,纯属杀鸡用牛刀,而且迭代慢、成本高、本地根本跑不起来。
DuckDB 出现的核心动机,就是填这个洞:在你本机的进程里,用接近数据库内核的效率,把「单机放得下」的数据分析做到极致。
1.2 SQLite 解决了 OLTP 的「嵌入式」,但 OLAP 的「嵌入式」长期空白
SQLite 是地球上部署最广泛的数据库,它的成功密码就两条:零依赖 + 嵌入式(in-process)。应用直接 link 一个库,没有服务端、没有连接池、没有 DBA,打开一个文件就能用。
但 SQLite 的存储引擎是为 OLTP 点查优化的——B+ 树行存、强调事务与点查延迟。你让它 SELECT sum(amount) FROM orders WHERE dt BETWEEN ... GROUP BY region 扫一亿行,它会很难受:行存意味着为了算一个 SUM 要把整行(包括你不需要的列)都从磁盘读上来。
于是出现了一个奇怪的局面:
- 嵌入式 OLTP 有 SQLite;
- 服务端 OLAP 有 ClickHouse / Druid / Doris;
- 嵌入式 OLAP 长期没有正经选手。
DuckDB(由荷兰 CWI 的 Mark Raasveldt 与 Hannes Mühleisen 主导,2019 年 SIGMOD 论文 "DuckDB: an Embedded Analytical Database" 正式亮相,MIT 协议)就是来补这个位的。它的设计哲学原话搬自官方 why_duckdb:「DuckDB adopts SQLite's ideas of simplicity and embedded operation, but is designed for analytical query workloads.」
1.3 为什么是现在
三件事同时成熟,让 DuckDB 在 2023 年后突然爆发:
- 单机内存白菜化:一台普通云主机轻松 64~256 GB 内存,SSD 持久盘轻松上 TB。大量「分析任务」其实根本不需要分布式,只是以前没有趁手的单机引擎。
- 开放列式格式成为事实标准:Parquet / ORC 让「数据文件」本身就是列式、自带统计信息、可被高效裁剪。数据库不再需要把数据「导入」自己的私有格式才能分析——直接读文件就行。
- Lakehouse 下沉:Iceberg / Delta Lake 把「表」的定义搬到了对象存储(S3 / 本地磁盘)上的 Parquet 文件集合。分析引擎和存储解耦,DuckDB 这种轻量引擎正好能直接当 Lakehouse 的查询层。
1.4 边界:DuckDB 不是来替代谁的
澄清几个常见误解,也正好说明它和本站已发过的选题不冲突:
| 维度 | DuckDB | ClickHouse | pandas | Spark |
|---|---|---|---|---|
| 部署形态 | 库/进程内,无服务端 | 服务端集群 | 内存 DataFrame | 分布式集群 |
| 最佳数据量 | GB ~ 数 TB(单机) | TB ~ PB(可横向) | MB ~ 数百 MB | GB ~ PB |
| 读外部文件 | 一等公民(Parquet/CSV/JSON/S3) | 需引擎表 | 需先 load 进内存 | 一等公民 |
| 并发写 | 单写多读 | 多写 | 无 | 多写 |
| 典型用法 | 应用内分析、本地湖仓、ETL | 实时分析服务 | 探索性分析 | 大规模批处理 |
一句话:DuckDB 是「单机分析的操作系统」,不是「分布式分析的替代品」。 你不会用它去扛每秒上万次的在线写入;但你会用它替代脚本里那一堆 pandas.read_csv + 半吊子聚合,以及替代「为了查一个 Parquet 还得起一个 Spark session」的笨重流程。
二、核心概念:DuckDB 到底是什么
2.1 In-process:没有服务端,文件就是数据库
DuckDB 以库的形式存在。Python 里 pip install duckdb 后:
import duckdb
con = duckdb.connect("analytics.duckdb") # 打开一个文件数据库;不传参则是纯内存库
con.execute("CREATE TABLE t(id INTEGER, v DOUBLE)")
con.execute("INSERT INTO t VALUES (1, 1.5), (2, 2.5)")
print(con.execute("SELECT sum(v) FROM t").fetchall())
# [(4.0,)]
注意到没有:没有 host、port、username、pool。它和你的 Python 解释器跑在同一个进程、同一个地址空间里,连 IPC 都没有。这意味着:
- 零序列化开销:数据在 DuckDB 内部和你的应用之间不需要通过网络或 IPC 拷贝。
- 即开即用:CI 里、Lambda 函数里、Notebook 里、甚至浏览器(Wasm 版)里都能直接跑。
- 文件即数据库:
.duckdb文件可以scp走、可以 commit 进 Git LFS、可以丢进对象存储当快照。
2.2 直接读文件,零导入
这是 DuckDB 最「爽」的特性。不需要 CREATE TABLE 再 COPY,直接用文件名当表名:
-- 直接查一个 Parquet 文件
SELECT region, sum(amount) AS gmv
FROM 's3://bucket/orders/2026-07.parquet'
GROUP BY region
ORDER BY gmv DESC;
甚至支持目录即分区表:
-- 目录下按 dt=xxx/ 分区的 Parquet,DuckDB 自动识别分区列
SELECT dt, count(*)
FROM 's3://bucket/orders/dt=*/part-*.parquet'
GROUP BY dt;
它支持的「外部源」包括:
- 文件:Parquet、CSV、JSON(含 JSON Lines)、Arrow IPC、Excel;
- 远程:通过
httpfs扩展读 S3 / GCS / HTTP 上的文件; - 其他数据库:通过扩展读 PostgreSQL(
postgres_scanner/pg_duckdb)、MySQL、SQLite; - 湖仓表:通过
iceberg/delta扩展直接读 Iceberg / Delta 表。
2.3 向量化执行 + 列式存储(重点)
这是 DuckDB 性能的根。两个概念要分开理解:
列式存储(存储层):数据按列而不是按行落盘。分析查询通常只碰少数几列,SELECT avg(price) 时磁盘上只读 price 那一列,其他列(用户名、地址、备注)根本不进内存。
向量化执行(执行层):传统 Volcano 执行模型一次在算子间传递一行(tuple),CPU 大部分时间花在函数调用、虚表分发、分支预测失败上。DuckDB 改为一次传递一个 DataChunk——默认 2048 行的列向量批(batch)。一个 SUM 聚合算子收到的是「一整块 double 数组」,内部用紧凑循环(甚至 SIMD)一口气算完,函数调用次数降到 1/2048。
-- 你写的还是普通 SQL,但引擎在底层以 2048 行一批的方式向量化处理
EXPLAIN ANALYZE
SELECT user_id, count(*) AS cnt
FROM 'events.parquet'
WHERE event = 'purchase'
GROUP BY user_id
ORDER BY cnt DESC
LIMIT 10;
工程直觉:向量化 + 列式,本质上是在用「批处理 + 连续内存」讨好现代 CPU 的缓存和流水线。你写的代码没变,但数据在内存里的排布和算子的调用粒度变了,性能差出 1~2 个数量级是常态。
2.4 事务与 ACID(单写多读)
DuckDB 支持事务,BEGIN / COMMIT / ROLLBACK 都能用,写入通过 checkpoint 持久化。并发模型是单写者(single-writer)+ 多读者:同一时刻允许一个进程写、多个进程/连接读。它不适合做高并发在线写入(那是 OLTP 的活),但做「低频写、高频分析读」非常稳。
2.5 类型系统与 1.5 新特性
DuckDB 的类型系统比传统 SQL 数据库现代得多:原生支持 LIST、STRUCT、MAP、嵌套类型、ENUM、UUID、TIMESTAMP 全家桶,以及 1.5「Variegata」带来的两个重磅新类型:
VARIANT:半结构化数据的统一容器,可以塞任意 JSON 风格的值,查询时再按需抽取,比老的JSON类型在性能与易用性上更优。GEOMETRY:内置空间类型,配合spatial扩展做点/线/面运算,无需再外挂 PostGIS 才能玩 GIS。
示例:
-- 1.5 的 VARIANT:直接吃进任意 JSON,查询时抽取
CREATE TABLE logs(payload VARIANT);
INSERT INTO logs VALUES ('{"user": "alice", "score": 92, "tags": ["vip","cn"]}');
SELECT
payload.user,
payload.score,
payload.tags[1]
FROM logs
WHERE payload.score > 90;
三、架构分析:一条 SQL 在 DuckDB 里走了哪几步
理解架构,才能在出问题时知道去哪调。DuckDB 的查询路径和主流分析型数据库一致,分四段:
SQL 文本
│
▼
Parser(词法/语法)──► 抽象语法树 AST
│
▼
Binder(绑定/语义检查)──► 把表名、列名、函数解析成内部表示,做类型推导
│
▼
Optimizer(优化器)
│ ├─ 基于规则(RBO):谓词下推、列裁剪、表达式重写、子查询扁平化
│ └─ 基于代价(CBO):根据统计信息选 join 顺序、join 算法
▼
Execution Engine(向量化执行引擎)
│ └─ 以 Pipeline + DataChunk 批量处理,多线程并行
▼
存储层(列存块 + 压缩 + 统计信息 + 外部文件扫描)
3.1 优化器:把「能不下推的别下推」做到极致
DuckDB 优化器里对你性能影响最大的是这几条规则:
- 谓词下推(Predicate Pushdown):
WHERE dt = '2026-07-01'会被尽量推到扫描算子最底层,最好在读 Parquet 之前就用行组的 min/max 统计信息把整块文件/行组过滤掉——文件根本不进内存。 - 列裁剪(Projection Pushdown):你只
SELECT price,引擎只读price那一列的物理数据。 - 分区裁剪(Partition Pruning):目录分区
dt=2026-07-01/直接让不匹配的分区目录根本不被打开。 - 子查询去关联(Flattening):把
EXISTS/ 相关子查询改写成 join,避免嵌套循环。
3.2 执行引擎:向量化 + 多核
如前所述,执行引擎以 DataChunk(2048 行批)为单位在各算子间流动。扫描算子把 Parquet 解码成列向量,过滤、聚合、join 算子都吃列向量、吐列向量。
并行方面,DuckDB 会在扫描和流水线两个层面用多线程:
- 扫描并行:一个大文件按行组(row group)切片,多个线程同时解码不同片段;
- Pipeline 并行:一条算子链(pipeline)被复制成多份,各自处理不同数据块。
线程数由 threads 参数控制,默认等于 CPU 核心数。在容器里被限制了 CPU 配额时,记得手动设小,否则线程争抢反而更慢。
3.3 存储层:压缩与统计信息是性能的隐藏功臣
DuckDB 对每列按段(segment)存储,并自动选轻量压缩:常量压缩、字典压缩、游程编码(RLE)、位打包(bit-packing)等。更关键的是每段都记录 min/max/null_count 等统计信息(zone map),这是谓词下推能「跳过整块数据」的依据。
对 Parquet 这类外部文件,DuckDB 直接读取文件自带的 footer 统计信息做同样的裁剪,所以「直接查 Parquet」几乎不比「导入后查」慢——这才是它敢喊「零导入」的底气。
3.4 和 SQLite 架构的本质差异
一句话总结差异点:SQLite 的存储与执行是为「点查一行 + 小事务」优化的(行存 B 树、逐行 Volcano);DuckDB 是为「扫全表 + 聚合 + join」优化的(列存、向量化批处理、扫描并行)。同样一句 SELECT sum(x) FROM t,SQLite 要遍历所有行所有列,DuckDB 只读 x 列、按 2048 行一批、多核并行、还能跳过不符合谓词的数据块。
四、代码实战:从「能跑」到「好用」
下面用 Python + SQL 走一遍真实工作流。所有示例都可本地复制运行(DuckDB 1.5)。
4.1 环境准备
# CLI(最推荐,官网下二进制或 brew)
brew install duckdb
duckdb # 进入交互式
# Python
pip install duckdb
CLI 里几个高频命令:
.read script.sql -- 执行 SQL 文件
.export json out.json -- 导出
.tables -- 列出表
DESCRIBE tbl; -- 看 schema
4.2 实战一:零导入直查 Parquet + 分区目录
假设 S3 上有一堆按天分区的订单 Parquet:
import duckdb
# 直接把分区目录当表,dt 自动成为分区列
sql = """
SELECT
dt,
region,
sum(amount) AS gmv,
count(*) AS orders,
approx_quantile(amount, 0.99) AS p99_amount
FROM read_parquet('s3://my-bucket/orders/dt=*/part-*.parquet',
hive_partitioning=true)
WHERE dt BETWEEN '2026-07-01' AND '2026-07-07'
GROUP BY dt, region
ORDER BY dt, gmv DESC;
"""
con = duckdb.connect()
# 配置 S3 访问(用 httpfs 扩展)
con.execute("""
INSTALL httpfs;
LOAD httpfs;
SET s3_region = 'us-east-1';
SET s3_access_key_id = '${ENV:AWS_KEY}';
SET s3_secret_access_key = '${ENV:AWS_SECRET}';
""")
df = con.execute(sql).df() # 直接出 pandas DataFrame
print(df.head())
read_parquet 的 hive_partitioning=true 是神器:目录里的 dt=2026-07-01/ 会被自动解析成 dt 列,无需自己 UNION ALL。
4.3 实战二:CSV/JSON 自动推断 schema
-- CSV 自动推断类型、自动探测分隔符
SELECT product, avg(price) AS avg_price
FROM read_csv_auto('sales_*.csv', sample_size=100000)
GROUP BY product;
-- JSON Lines 直接当表
SELECT json_extract_string(payload, '$.user') AS u,
(payload->>'score')::INT AS score
FROM read_json_auto('events.jsonl')
WHERE score > 90;
read_csv_auto 会采样前若干行推断列类型、分隔符、是否有表头,省掉手写 schema 的痛苦。sample_size 调大能让类型推断更稳。
4.4 实战三:和 pandas / Polars / Arrow 零拷贝互操作
这是 DuckDB 在 Python 生态里最香的点——它和 Arrow 内存格式打通,和 DataFrame 之间可以零拷贝转换:
import duckdb, pandas as pd
df = pd.DataFrame({"id": [1,2,3], "v": [10,20,30]})
# pandas -> DuckDB(注册为视图,不拷贝数据)
con = duckdb.connect()
con.register("df_view", df)
print(con.execute("SELECT sum(v) FROM df_view").fetchone()) # (60.0,)
# DuckDB 结果 -> pandas
res = con.execute("SELECT id, v*2 AS v2 FROM df_view").df()
# DuckDB 结果 -> Arrow Table(零拷贝)
tbl = con.execute("SELECT * FROM df_view").arrow()
实用建议:不要再用
pandas.read_parquet把 5 GB 文件整块读进内存再聚合了。让 DuckDB 在文件层面先聚合、只把聚合后的小结果交给 pandas,内存占用和速度都会好一个量级。
4.5 实战四:把 DuckDB 当本地 Lakehouse(CTAS + Iceberg)
用 CREATE TABLE AS(CTAS)把清洗后的结果落回 Parquet,形成「可复现的数据管道」:
-- 把原始日志清洗成按天分区的宽表
COPY (
SELECT
user_id,
event,
to_timestamp(ts/1000) AS ts,
payload
FROM read_json_auto('raw/events_*.jsonl')
WHERE event IS NOT NULL
) TO 'warehouse/clean_events/dt=2026-07-01/'
(FORMAT PARQUET, PARTITION_BY (dt), CODECS ('snappy'));
-- 之后直接查清洗后的湖仓
SELECT event, count(*) FROM 'warehouse/clean_events/dt=*/' GROUP BY event;
接 Iceberg(1.5 起 duckdb-iceberg 能力大幅增强):
INSTALL iceberg;
LOAD iceberg;
-- 直接把 Iceberg 表当普通表查询(catalog 配置略)
SELECT * FROM iceberg_scan('s3://lake/warehouse/db/orders');
4.6 实战五:应用内嵌分析,替代脚本里的 pandas 聚合
一个常见场景:一个 Go/Python 服务需要定期对本地落盘的日志做统计报表。以前要起个 Spark 或写一堆 pandas,现在直接嵌 DuckDB:
# 在 Web 服务里做即席分析(FastAPI 伪代码)
import duckdb
DB = duckdb.connect("app.duckdb", read_only=False)
def daily_report(date: str):
return DB.execute(f"""
SELECT region, count(*) AS cnt, sum(amount) AS gmv
FROM events
WHERE date = '{date}'
GROUP BY region
""").df()
注意 read_only=False 用于写;纯分析只读场景务必用 read_only=True,这样多个进程可以并发打开同一个库文件做读。
4.7 实战六:pg_duckdb / dbt-duckdb 做 ELT
- pg_duckdb:把 DuckDB 引擎装进 PostgreSQL,让 PG 直接
SELECT ... FROM duckdb.table(...)查 Parquet/S3,OLTP 和 OLAP 一张嘴。 - dbt-duckdb:用 dbt 这套成熟的 ELT 编排,把模型跑在 DuckDB 上,本地开发体验和线上一致。
-- pg_duckdb:在 Postgres 里直接分析对象存储上的 Parquet
SELECT u.name, sum(o.amount)
FROM users u
JOIN duckdb.parquet('s3://bucket/orders/*.parquet') o
ON o.user_id = u.id
GROUP BY u.name;
4.8 实战七:1.5 的空间分析(GEOMETRY)
INSTALL spatial;
LOAD spatial;
CREATE TABLE pois(name VARCHAR, geom GEOMETRY);
INSERT INTO pois VALUES
('office', ST_Point(116.39, 39.91)),
('shop', ST_Point(116.40, 39.92));
-- 找出距离 office 1km 内的点(示意)
SELECT a.name, b.name,
ST_Distance(a.geom::GEOGRAPHY, b.geom::GEOGRAPHY) AS m
FROM pois a, pois b
WHERE a.name='office' AND b.name<>'office';
五、性能优化:把 DuckDB 榨干的正确姿势
DuckDB 开箱已经很快,但用错姿势照样能慢十倍。按性价比排序:
5.1 第一性原理:让「下推」生效
性能优化的 80% 都来自「尽量早地砍掉数据」:
- 分区列参与过滤:用
dt=...目录分区,查询里带dt条件,触发分区裁剪。 - 排序列 / 聚簇键:把常过滤的列(如
user_id、event_type)作为 Parquet 的写入排序键,让 min/max 统计更有效。 - 只
SELECT需要的列:列裁剪的前提是你别写SELECT *。 - 文件别太小也别太大:Parquet 单文件 128 MB~1 GB 体验最佳;几 KB 一个的小文件会让元数据开销压垮扫描。
-- 好的写法:分区 + 列裁剪 + 谓词下推全生效
SELECT region, sum(amount)
FROM 'warehouse/orders/dt=2026-07-01/part-*.parquet'
WHERE region = 'cn-east' -- 分区/列统计可裁剪
GROUP BY region;
-- 差的写法:SELECT * + 在应用层过滤
-- SELECT * FROM 'warehouse/orders/dt=*/part-*.parquet'
5.2 配置:threads / memory_limit
con = duckdb.connect()
con.execute("SET threads = 4") # 容器里 CPU 受限时手动设
con.execute("SET memory_limit = '4GB'") # 控制内存上限,避免 OOM
con.execute("SET preserve_insertion_order = false") # 不需要保序时关掉,聚合更快
memory_limit 设得太小会频繁落盘(spill),设得太大在容器里会被 OOM Killer 干掉。经验值:留 20% 给系统和 Python 本身。
5.3 写入:用 Appender 批量插,别逐行 INSERT
逐行 INSERT 在 DuckDB 里是反模式(每次都要走事务/解析)。大批量写用 Appender 或一次性 COPY / INSERT INTO ... SELECT:
con = duckdb.connect("app.duckdb")
app = con.append("events") # 拿到 Appender
for row in huge_iterator:
app.append(row) # 攒批写,远快于逐行 execute INSERT
app.close()
5.4 中间结果落地:CTAS 比反复子查询稳
长管道里,把中间大结果用 CREATE TABLE ... AS 落盘,比层层嵌套子查询更易调试、可断点续跑,也避免重复计算:
CREATE TABLE step1 AS SELECT ... FROM raw WHERE ...;
CREATE TABLE step2 AS SELECT ... FROM step1 JOIN dim ...;
-- 出问题改 step2 即可,不用重跑整条
5.5 并发读:read_only 模式
多个进程/线程同时分析同一个 .duckdb 文件,全部用 read_only=True 打开即可并发读。一旦有写入者,读者会被锁等待——所以写和分析读尽量错峰,或写方用独立文件、读方读快照。
5.6 看计划:EXPLAIN ANALYZE
一切优化从看执行计划开始:
EXPLAIN ANALYZE
SELECT region, count(*) FROM 'orders/dt=*/part-*.parquet'
WHERE amount > 100 GROUP BY region;
重点看:是否出现了 PARQUET_SCAN 且 rows_filtered 很大(说明下推生效);是否某算子耗时占比异常高(可能缺索引/缺分区/数据倾斜)。
六、总结展望:DuckDB 该放在你架构的哪一层
6.1 它擅长什么、不擅长什么
适合:
- 单机 GB~TB 级数据的即席分析、报表、ETL/ELT;
- 应用内嵌的分析能力(CLI 工具、数据产品后端、Notebook);
- 本地 Lakehouse 查询层(直查 Parquet / Iceberg / Delta);
- 替代「pandas 读大文件再聚合」「为查一个文件起 Spark」的笨重流程;
- 边缘 / Serverless / CI 里跑分析(无服务端、毫秒启动)。
不适合:
- 高并发在线写入(OLTP);
- 需要横向扩展到多机 PB 级、且要求严格 SLA 的实时分析服务(那是 ClickHouse / StarRocks 的活);
- 多写者强一致的事务系统。
6.2 生态在 2026 年的关键进展
- MotherDuck:把 DuckDB 搬上云,本地引擎和云端无缝同步,边缘分析 + 云端协作成为现实。
- pg_duckdb / dbt-duckdb:让 DuckDB 融入 Postgres 与 dbt 生态,ELT 工作流本地就能跑通。
- Iceberg / Delta 扩展增强:1.5.3 起
duckdb-iceberg能力大幅增强,DuckDB 正成为「Lakehouse 的本地查询标准入口」。 - 1.5「Variegata」:全新 CLI、VARIANT 半结构化类型、GEOMETRY 空间类型,把「分析 + 半结构化 + GIS」三件事收进一个引擎。
- 1.4「Andium」LTS:长期支持版维护到 2026 年 9 月,企业落地有稳定基线。
6.3 给程序员的实在建议
- 下次想
pandas.read_parquet再groupby的时候,先想想这文件有多大。 超过内存 1/3,直接交给 DuckDB,只在最后取聚合结果。 - 把你脚本里的「读文件 + 过滤 + 聚合」翻译成一条 SQL。 你会惊讶于代码变短、变快、还更好维护。
- 本地用 DuckDB 开发 dbt / Lakehouse 管道,跑通后再上云,开发循环从「分钟级」降到「秒级」。
- 别拿它当 MySQL 用。 它快是因为放弃了高并发写和分布式,扬长避短才是工程智慧。
DuckDB 的聪明之处,不在于发明了什么黑科技,而在于把两件早就成熟的事做到了极致:SQLite 式的「零依赖嵌入式」哲学,加上分析型数据库几十年的「列式 + 向量化」积累。它把一个过去要么太重、要么太弱的区间,填成了一条顺滑的本地分析高速公路。
当你下次面对一个「不大不小、上分布式不值当、用 DataFrame 又爆内存」的数据问题时,记得:也许你需要的不是更大的集群,而是一个被你忽略已久的、就躺在进程里的那只鸭子 🦆。
参考资料:DuckDB 官方文档(duckdb.org)why_duckdb、SIGMOD 2019 "DuckDB: an Embedded Analytical Database"、DuckDB 1.5.0 发布说明、duckdb-iceberg / pg_duckdb / dbt-duckdb 项目仓库。本文代码基于 DuckDB 1.5 验证思路,具体 API 以你所装版本文档为准。