作为一个常年和数据打交道的人,我最近把不少临时分析任务从 Pandas 和 SQLite 迁移到了 DuckDB 上,正好赶上 1.4.3 LTS 这个版本稳定下来,用下来确实有不少感触。如果你还没接触过这个号称“轻量级分析型数据库”的新选择,这篇博文应该能帮你少走不少弯路。它不是什么要部署一整套服务的重型组件,本质就是一个可以嵌入到进程里的列式 SQL 引擎,但对数据分析、ETL、报表查询这些场景来说,实用性直接拉满,而且对新手极其友好。
我不会给你铺一堆干巴巴的理论,而是直接从“它解决什么问题”和“我实际怎么用”两个角度来拆解。下面这些内容覆盖了版本定位、环境搭建、核心功能、真实案例、性能调优和常见坑点,都是我在真实项目中验证过的做法,你可以直接照着操作复现。
1. 为什么说 DuckDB 是轻量级分析型数据库的新选择
1.1 数据分析场景里的真实痛点
先聊聊我见过的普遍痛点。以前做数据分析,最常见的组合是 SQLite 存数据、Pandas 做清洗和转换、然后再倒腾到可视化工具里画图表。这套流程初期挺顺手,但数据量一旦涨上去,问题就接踵而至:SQLite 是行式存储,碰上几千万行的聚合统计,查询慢得让人怀疑人生;Pandas 单机处理几个 GB 的数据时,内存占用经常直接爆掉,机器风扇狂转、进程卡死都是家常便饭。
还有一类场景也特别头疼:你手里可能有一堆 CSV、Parquet、JSON 文件,散落在各个目录,想快速统计一下某些字段的分布情况。传统做法要么写一段 Python 脚本去逐行解析,要么把这些文件导入各种数据库再查询,费时费力。尤其是 Parquet 这种列式格式,用普通工具读进去转成 DataFrame,光是 I/O 和反序列化就能消耗大量内存。
这些痛点指向同一个需求:我想在本地、单机上,用一种更轻的方式,对中等规模的数据做快速、灵活、类 SQL 的分析。DuckDB 就是冲着这个需求来的。
1.2 DuckDB 的定位与核心优势
DuckDB 的定位非常清晰:嵌入式分析型数据库。所谓嵌入式,就是它不要求你单独起一个服务进程,而是作为库直接链接进你的应用,和 SQLite 的形态很像。但不同的是,它内部是彻底的列式存储和向量化执行引擎,专门为 OLAP 场景设计,而不是 OLTP。
几个核心优势我列一下:
- 零部署:没有独立的服务器进程,没有复杂的配置文件,一个二进制文件或者一个 Python 包就能跑,装完即用。
- 分析性能强:列式存储配合向量化执行,做聚合、连接、排序、窗口函数这类分析操作时,比行式存储的关系型数据库快一个量级,尤其在宽表场景下效果显著。
- 直接查询文件:它原生支持直接读取 Parquet、CSV、JSON、Arrow 等格式的文件,不需要预先导入,这在数据湖和文件碎片化场景下简直神器。
- 完整 SQL 支持:它不是玩具,支持标准 SQL,包括复杂子查询、窗口函数、CTE、UNION、PIVOT 等,写起来很顺手,甚至支持跨数据库连接(比如直接连 PostgreSQL 或 SQLite 的表)。
- 内存高效:得益于列式存储和压缩、向量化处理,它在同样内存条件下能处理比 Pandas 大得多的数据量。
我用一个不恰当的类比给你加深印象:如果说 SQLite 是“单机版的 OLTP 数据库”,那 DuckDB 就是“单机版的 OLAP 数据库”。它刻意避开了高并发写入场景,专注在“数据进来之后怎么高效分析”这件事上,所以能把这部分体验做到极致。
1.3 1.4.3 LTS 在版本演进中的看点
可能有人会问,DuckDB 版本迭代一直很快,为什么 1.4.3 LTS 值得专门说一说。这里有个背景:DuckDB 早期版本更新频繁,很多 API 和行为在不同版本间有变化,对在生产环境里使用的人来说其实有一点版本兼容性的压力。而 LTS(Long Term Support)版本的出现,就是为了给那些希望 API 稳定、行为可控、长期维护的使用者一个更安心的选择。
1.4.3 LTS 是我个人用得比较久的稳定版本,几个值得关注的点:
- 对早期 1.x 系列的 API 做了大量稳定化工作,Python、CLI 等各语言绑定的接口行为趋于一致,迁移成本明显降低。
- 查询优化器在这一版里更成熟,复杂查询(多表 JOIN、多层窗口函数)的执行计划选择更合理,很多场景下不需要手动调优就能拿到不错的性能。
- 在文件格式兼容性上做了不少增强,处理 Parquet 的各类版本和嵌套结构更加稳妥,对 JSON 解析的鲁棒性也更好。
- 修复了不少过去版本中容易遇到的问题,比如某些特殊数据类型排序异常、大查询状态下内存溢出的边界情况等。
如果你是在企业内部或者长期项目中引入,选一个 LTS 版本是比较稳妥的策略;如果你是个人学习或者短期分析任务,用最新稳定版也行,但我觉得从 1.4.3 LTS 入手学习完全够用,后面代码也不会随便被版本升级搞坏。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与基础实操
2.1 安装 DuckDB:不是一般的简单
DuckDB 安装确实是我接触过的数据库里最简单的之一,基本就是下载即用。它提供了预编译的 CLI 二进制文件,也提供了 Python、R、Java、Node.js、Go 等主流语言的绑定。我自己日常用得最多的是 CLI 和 Python 绑定,这里分别说一下。
先看 CLI 的安装。进入 DuckDB 的官方发布页面,下载对应平台的 CLI 压缩包,解压后得到一个 duckdb 可执行文件,直接放到 PATH 里就行。比如在 Linux 环境下:
bash复制wget https://github.com/duckdb/duckdb/releases/download/v1.4.3/duckdb_cli-linux-amd64.zip
unzip duckdb_cli-linux-amd64.zip
sudo mv duckdb /usr/local/bin/
duckdb
进入之后你会看到一个 SQL 提示符,说明已经装好了。如果你只需要一个最小环境,这一步就结束了,后续所有操作都在这一个命令里完成。
Python 绑定的安装更简单,直接 pip:
bash复制pip install duckdb
然后在 Python 里验证一下:
python复制import duckdb
print(duckdb.__version__)
如果输出 1.4.3 之类的版本号就说明安装成功。注意,如果你用的是 LTS 版本,建议锁定版本号以避免新特性带来的 API 变化:
bash复制pip install duckdb==1.4.3
2.2 CLI 端的第一个查询
装好之后,直接在终端里敲 duckdb 进入交互式命令行,然后可以跑一个最简单的查询:
sql复制SELECT 'Hello, DuckDB' AS greeting;
你会看到结果输出得非常干净。接下来,用 DuckDB 的内置函数生成一些数据试试:
sql复制SELECT
range AS id,
random() * 100 AS value
FROM range(10);
这是一条典型的生成序列查询,range() 是 DuckDB 的一个表函数,返回一个数字序列。从这里就能感受到,它内置了很多面向分析的便捷函数,让测试和临场探索变得非常方便。
如果你不想进入交互模式,也可以直接用命令行传 SQL 参数执行:
bash复制duckdb -c "SELECT count(*) FROM range(1000000)"
这个模式下很适合在脚本里快速跑点验证查询。
2.3 Python 绑定:分析工作流里的主力用法
Python 绑定是我个人觉得 DuckDB 最强大的地方。它的 API 设计很人性化,你既可以直接执行 SQL,也能无缝衔接 Pandas DataFrame 和 Arrow Table。
一个最基础的例子,直接对 DataFrame 执行 SQL 查询:
python复制import duckdb
import pandas as pd
df = pd.DataFrame({
"category": ["A", "B", "C"] * 100,
"value": range(300)
})
result = duckdb.sql("""
SELECT category, SUM(value) AS total, AVG(value) AS avg_val
FROM df
GROUP BY category
ORDER BY total DESC
""").df()
print(result)
注意这里不需要事先注册表名,DuckDB 会自动把你在 SQL 里引用的 DataFrame 识别为可查询的对象,非常方便。这种方式特别适合在 Jupyter Notebook 里做探索性分析:先加载数据到 Pandas,然后交给 DuckDB 去做复杂的查询,比直接用 Pandas 写链式操作要简洁得多,性能也更好。
除了 .df() 把结果转成 Pandas,还可以用 .arrow() 转成 Arrow 表,或者用 .fetchall() 拿原始元组列表,按需选择即可。
你可能还会用到数据库文件的持久化。DuckDB CLI 默认是纯内存模式,如果希望数据落盘,只需要在启动时指定一个数据库文件:
bash复制duckdb mydatabase.duckdb
或者在 Python 中这样连接:
python复制con = duckdb.connect("mydatabase.duckdb")
con.sql("CREATE TABLE t AS SELECT 1 AS id, 'x' AS name")
con.close()
这个文件和 SQLite 的 .db 文件类似,单文件即可迁移,非常契合“轻量级”的定位。
3. 核心能力详解:为什么它能跑得这么快
3.1 列式存储与向量化执行引擎
要理解 DuckDB 的性能,得先从存储和执行两个层面看。存储层面,DuckDB 使用列式存储,即同一列的数据连续存放在一起。这和行式存储(比如 SQLite、PostgreSQL 的堆表)有本质区别。
为什么列式存储对分析查询有优势?因为分析类查询往往只关心少数几个列。比如有一张 100 列、1 亿行的表,你只想知道其中 3 列的统计值。列式存储只需要读取这 3 列对应的数据块,而行式存储要把每一行的完整数据(包括那 97 列不相关的字段)都读出来,I/O 开销差距可能达到几十倍。这也是为什么很多数据仓库(ClickHouse、Redshift 等)都采用列式存储。
执行层面,DuckDB 采用向量化执行引擎,意思是它一次处理一批数据(向量),而不是一行一行地处理。这能显著减少函数调用开销、充分利用 CPU 缓存和 SIMD 指令,提升吞吐量。
我做个简单测试给你看。假设生成一张 1000 万行的表做聚合:
python复制import duckdb
con = duckdb.connect()
con.execute("CREATE TABLE sales AS SELECT i AS id, i % 100 AS region, random() * 1000 AS amount FROM range(10000000) t(i)")
然后跑一个聚合查询:
python复制result = con.execute("""
SELECT region, COUNT(*), SUM(amount), AVG(amount)
FROM sales
GROUP BY region
""").fetchall()
print(result)
在普通笔记本上,这种千万级数据的聚合基本是秒级完成的。如果用 SQLite 做同样的事,查询耗时可能是几十倍不止。这就是列式存储加向量化执行的威力。
3.2 直接查询 Parquet、CSV、JSON 文件
DuckDB 对文件的原生支持是我觉得最牛的地方之一。它可以直接查询 Parquet 文件,而不需要导入数据。Parquet 本身是列式存储格式,DuckDB 读取它时可以做到只读取查询涉及的列,进一步减少 I/O,这个特性对大数据场景非常重要。
看看实际用法:
sql复制-- 直接查询 Parquet 文件
SELECT category, SUM(value)
FROM 'data/*.parquet'
GROUP BY category;
这里的通配符 * 可以匹配多个文件,DuckDB 会自动把这多个文件当成一张表来处理,非常方便。而且需要注意的是,如果你的 Parquet 文件里有分区目录(比如按日期分目录),DuckDB 也能自动识别分区列,并在查询时做分区裁剪。
CSV 文件同样可以这样查询:
sql复制SELECT *
FROM 'data/sales.csv'
LIMIT 100;
如果你多次查询同一个文件,可以创建一个视图,避免每次重复写路径:
sql复制CREATE VIEW sales AS SELECT * FROM 'data/sales.csv';
SELECT * FROM sales WHERE region = '华东' LIMIT 10;
JSON 文件稍微特殊一点,但语法也很直接:
sql复制SELECT * FROM 'data/events.json' LIMIT 10;
DuckDB 对嵌套 JSON 结构的处理能力很强,可以自动推断 schema,也能通过 json_extract 之类的函数访问嵌套字段,省去了不少预处理工作。
3.3 窗口函数与复杂 SQL 支持
DuckDB 的 SQL 兼容性做得相当不错,这让我在从 PostgreSQL 迁移查询时几乎没有压力。比如窗口函数,在 PostgreSQL 里能写的分析需求,在 DuckDB 里基本都能写,而且语法完全一样。
举个例子,我想算每个区域内销售排名前 10 的记录:
sql复制SELECT region, salesperson, amount,
RANK() OVER (PARTITION BY region ORDER BY amount DESC) AS rk
FROM sales
QUALIFY rk <= 10;
注意这里用了 QUALIFY,这是一个在 SQL 标准里比较“新”的子句,很多数据库不支持,但 DuckDB 原生支持,可以直接过滤窗口函数的结果。这个功能在做 Top-N 分析时特别方便。
DuckDB 还支持 PIVOT 和 UNPIVOT,也就是透视和逆透视,这在数据分析里太常用了。以前用 Pandas 做透视表需要 df.pivot_table(),而现在可以直接在 SQL 里做:
sql复制PIVOT sales
ON region
USING SUM(amount)
GROUP BY category;
这条 SQL 会把 region 的不同取值变成列,每个单元格是对应 category 下的销售额总和。相比在 Pandas 里折腾 index 和 columns,这种方式直观得多。
复杂查询方面,DuckDB 支持多表 JOIN、递归 CTE、LATERAL 子查询等,基本上你能想到的分析 SQL 它都能处理。这意味着过往很多基于 PostgreSQL 或 SQL Server 的分析脚本,可以几乎不修改地迁移到 DuckDB 上,迁移成本很低。
4. 实战场景:用它解决几个实际问题
4.1 用 DuckDB 替代 Pandas 做大规模数据清洗
Pandas 做数据清洗是很多人的日常,但当数据量超过内存或者处理链条特别长时,写起来很痛苦。我举个实际案例:假设你有一个几十 GB 的日志文件,分散在多个 CSV 里,你需要把它们组合、去重、按用户维度汇总。
传统 Pandas 做法可能是这样:
python复制import pandas as pd
df_list = []
for path in ["log_20250101.csv", "log_20250102.csv", ...]:
df_list.append(pd.read_csv(path))
df = pd.concat(df_list, ignore_index=True)
df = df.drop_duplicates(subset="user_id")
result = df.groupby("user_id").agg(...)
这个流程在数据量一大就会出现两个问题:一是 pd.concat 可能直接把内存耗尽;二是每一步链式操作都会产生临时对象,内存峰值很高。
用 DuckDB 的实现方式则是清晰且高效:
sql复制CREATE VIEW logs AS SELECT * FROM read_csv_auto('log_*.csv');
WITH deduped AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY event_time) AS rn
FROM logs
)
SELECT user_id,
COUNT(*) AS event_count,
SUM(amount) AS total_amount
FROM deduped
WHERE rn = 1
GROUP BY user_id;
这里 read_csv_auto 会自动推断列类型和表结构,DuckDB 在后台以流式方式处理文件,不会一次性把全部数据加载到内存。而且这个 SQL 从头到尾只执行一次扫描,中间结果也以列式向量存储,内存效率比 Pandas 高很多。
实际体验下来,我处理 10 GB 级别的 CSV 数据,DuckDB 比 Pandas 的内存峰值低一半还多,速度还更快,确实是一种解放。
4.2 跨数据源关联查询
另一个我经常用的场景是跨数据源关联。假设你有一份用户信息在 SQLite 咨询库里,订单数据在 PostgreSQL 里,还有一份设备行为数据在 Parquet 文件里。以前要把这几个数据源串起来分析,我得先把数据导成同一个格式,再合并,费时费力。
DuckDB 支持这些不同数据源的直接关联。它可以通过扩展直接连接到 PostgreSQL 和 SQLite,再和其他文件表做 JOIN。简单演示一下:
sql复制-- 安装并加载 postgres 扩展
INSTALL postgres;
LOAD postgres;
-- 连接 PostgreSQL 数据库
ATTACH 'dbname=analytics user=postgres host=localhost' AS pg (TYPE postgres);
-- 连接 SQLite 数据库
ATTACH 'users.db' AS sqlite_db (TYPE sqlite);
-- 跨库关联查询
SELECT u.user_id, u.name, o.order_amount
FROM sqlite_db.users u
JOIN pg.orders o ON u.user_id = o.user_id;
这样,本地文件、SQLite、PostgreSQL 的数据就可以在同一个 SQL 查询里被关联起来,不再需要繁琐的导出导入。对数据工程师来说,这相当于把 DuckDB 当成一个轻量的联邦查询层,非常实用。
4.3 和可视化生态的衔接
说到最近热词里的 “duckdb ui”,我估计很多人想知道 DuckDB 能不能用来做可视化。实际上 DuckDB 本身不提供图表界面,但它可以非常方便地对接各种前端工具。
最简单的可视化方式就是通过 Python 绑定的 .df() 方法把结果转成 Pandas DataFrame,再输入到 Matplotlib、Seaborn、Plotly 等库里。这个路径很顺滑,适合 Jupyter 里的快速探索。
如果想要交互式界面,还有几条路可以走:
- Streamlit:在 Streamlit 应用里直接写
st.dataframe(duckdb.sql("...").df()),分分钟做出带筛选器的数据看板。 - Evidence:这是一个基于 DuckDB 的开源 BI 工具,直接用 Markdown 写报表,底层查询走 DuckDB,对本地文件数据非常友好。
- QuackDB:一个开源的本地 DuckDB UI 工具,类似于 SQLite Browser 的体验,可以直接打开
.duckdb文件、浏览表结构、执行 SQL,适合非技术同事查看数据。 - MetaBase 的社区驱动:虽然不算官方支持,但也有人通过自定义驱动把 DuckDB 接入 BI 平台。
如果你只是想快速看个效果,我建议从 Streamlit 入手,因为它不需要额外的前端知识,写几行 Python 就能出一个能用的界面。比如:
python复制import streamlit as st
import duckdb
st.title("Sales Dashboard")
region = st.selectbox("选择区域", ["华东", "华北", "华南"])
sql = f"""
SELECT category, SUM(amount) AS total
FROM 'data/sales.parquet'
WHERE region = '{region}'
GROUP BY category
"""
st.bar_chart(duckdb.sql(sql).df())
这样写出来的应用,启动之后就是一个带下拉框的实时查询页面,后端完全由 DuckDB 支撑,简单直接。
5. 性能调优与参数配置
5.1 内存与临时目录的设置
DuckDB 默认情况下内存管理还算智能,但在处理特别大的查询时,你最好明确配置内存上限和临时目录,避免查询中途内存溢出或者数据落到系统临时盘导致不可控。
在 Python 绑定中,你可以通过 PRAGMA 语句设置:
python复制import duckdb
con = duckdb.connect()
con.execute("SET memory_limit = '8GB'")
con.execute("SET temp_directory = '/tmp/duckdb_temp'")
CLI 中也是一样的语法:
sql复制SET memory_limit = '8GB';
SET temp_directory = '/tmp/duckdb_temp';
这里有两个关键点需要说明。
memory_limit控制的是 DuckDB 在查询过程中可以使用的最大内存量。它不是 SQLite 那样的缓存,而是执行引擎能用的总内存预算。如果低于实际需要,DuckDB 会把中间结果溢写到临时目录,不会直接崩溃。但是溢写会大大降低性能,所以如果机器内存允许,尽量配置足够大的内存限制。temp_directory是指定中间结果的临时落盘位置。建议放在一个读写速度快的 SSD 上,不要放系统盘,而且要确保磁盘空间充足。
我的经验是,在 16 GB 内存的笔记本上处理 20 GB 左右的 Parquet 文件,设置 memory_limit = '8GB' 配合 SSD 临时目录,可以跑得动大部分聚合查询,只是个别超复杂 JOIN 会明显变慢。
5.2 并行度控制
DuckDB 默认会使用机器上所有的 CPU 核心进行并行计算。这对大型查询是好事,但如果你同时在跑多个任务,或者机器上还有其他服务,过高的并行度反而会导致资源争抢。
控制并行度通过 threads 参数:
sql复制SET threads = 4;
或者保留一部分核心给其他程序:
sql复制SET threads = 4;
SELECT * FROM ...
至于具体设多少,我的建议是从机器的物理核心数减 2 开始尝试。比如 8 核 16 线程的机器,设 threads=6,通常在查询性能和系统响应之前是一个不错的平衡点。
还有一个细节:threads 参数在不同查询间是共享的,如果你在一个连接里同时发多个异步查询,DuckDB 会自己分配线程池,但你最好还是控制一下同时运行的查询数量,否则线程切换开销会吞噬掉并行带来的收益。
5.3 实际调优经验
除了内存和线程,还有几个我实践中经常调整的 PRAGMA:
sql复制-- 开启并行扫描多个文件
SET enable_object_cache = true;
-- 控制输出格式,CLI 下更易读
SET max_line_size = 0;
-- 预测查询计划:查看计划的 SQL
EXPLAIN SELECT ... FROM ... ;
enable_object_cache 这个参数比较有意思。它的作用是缓存 Parquet 文件的一些元数据(比如统计信息、行数、schema),所以如果多次查询同一个文件,第二次开始会明显更快。如果你的数据文件是动态更新的,这个参数保持默认即可,不要手动开启,否则可能读到过期元数据。
另一个实用技巧是,频繁做 GROUP BY 分析时,如果被分组列是高基数字段(比如用户 ID),可以考虑用字典编码来减少内存占用。在 DuckDB 里可以这样处理:
sql复制SELECT count(*), hash(user_id) % 16 AS bucket
FROM table
GROUP BY bucket;
不过要注意,这只是一个内存优化技巧,如果不需要控制内存,还是直接 GROUP BY 原始列更简便。
关于查询计划,我强烈建议你在遇到性能问题时先跑一下 EXPLAIN ANALYZE,看看是不是扫描了太多数据、JOIN 顺序有没有明显问题。DuckDB 的优化器虽然不错,但它不是万能的,特别是在多表 JOIN 和子查询嵌套很深的情况下,有时候手动重写 SQL 比调参数更有效。
6. 常见问题与排查技巧实录
6.1 典型报错与解决方案
我在实际使用中遇到过不少报错,这里挑几个频率高的来说。
第一个是内存不足相关的报错,比如:
text复制Out of Memory Error: failed to allocate data of size ...
这个报错的直接原因通常是查询超出了内存限制。建议先看看 memory_limit 设置了多少,如果机器确实不够,可以启用临时目录让 DuckDB 把数据溢写到磁盘:
sql复制SET memory_limit = '4GB';
SET temp_directory = '/mnt/ssd/tmp';
另一方面,如果查询确实要处理的数据量远超内存,考虑改用流式方式:在 Python 里用 duckdb.sql(...).fetch_record_batch() 可以逐步拉取结果,降低峰值内存。
第二个是类型推断错误,常出现在读 CSV 的时候:
text复制Conversion Error: Could not convert string 'xxx' to INT64
read_csv_auto 虽然能自动推断类型,但遇到某些列混合了数字和文本时会猜测错误。解决办法是手动指定列类型,或者先抽样观察推断结果:
sql复制SELECT * FROM read_csv_auto('data.csv', sample_size=-1) LIMIT 10;
sample_size=-1 表示用全部数据来推断类型,虽然会慢一点,但准确率高不少。用这个确认完列类型后,再针对性建视图或者 CTAS 成表。
第三个是 Parquet 文件 schema 不一致导致的问题:
text复制Invalid Input Error: Parquet reader: schema mismatch in files ...
多文件查询时,DuckDB 会要求所有文件的 schema 一致(列名、列序、类型都要兼容)。如果遇到不一致,我一般是先用一个小脚本统一 schema,或者读取时指定列和类型,避免隐式转换失败。
6.2 数据量太大时的应对
虽然 DuckDB 能处理远超内存的数据,但数据量大到一定程度(TB 级别)单机确实会吃不消。这时候我的处理思路是:
- 一开始就利用 Parquet 的分区特性,把查询裁剪到涉及的分区,避免全量扫描。
- 用
WHERE子句保守一点,尽量做谓词下推,减少进入处理阶段的数据量。 - 如果必须全量扫描,尽量只取需要的列,DuckDB 的列式引擎在这方面能帮不少忙。
- 考虑使用
EXPORT DATABASE把数据转成 DuckDB 原生格式存盘,相比直接读 Parquet,在某些场景下查询性能还能更进一步。
另外提一嘴,EXPORT DATABASE 是比较容易被忽略的功能。它是一个整体迁移工具,可以把数据库中的所有表完全导出成一个目录(包含 schema 和数据),之后随时可以 IMPORT DATABASE 恢复。这在数据量大、需要迁移或者备份的场景下很有用。
6.3 一些容易忽略的小坑
有几个坑是新手很容易忽略的,这里集中说一下。
第一个坑是 Python 绑定的连接作用域。很多人喜欢在函数里直接 duckdb.sql(...),而不知道这个调用会创建一个临时连接。如果你同时开了多个连接,可能会出现表不存在或数据看不到的问题。我的建议是明确创建一个连接对象,在所有地方复用同一个连接:
python复制con = duckdb.connect()
# 后续所有查询都走 con.execute 或 con.sql
第二个坑是 CLI 默认的内存模式。如果你在 CLI 里建了表,但启动时没有指定数据库文件,那退出后所有数据都没了。刚开始不熟悉的时候很容易忘记保存,建了一堆表发现数据丢了。建议正式用的时候都带一个 .duckdb 文件路径,养成持久化的习惯。
第三个坑是 PRAGMA 配置是会话级别的。DuckDB 没有全局配置文件,所有 SET 的配置在连接关闭后就失效了。如果你希望每个连接都沿用同样的配置,最好在代码里封装一个初始化函数,或者在启动脚本里统一设置。
还有一个相对隐蔽的坑:DuckDB 对 ORDER BY 和 LIMIT 的组合在多线程环境下,结果顺序不一定是稳定的。如果你的业务逻辑依赖行顺序,一定要显式 ORDER BY,并且不要假设同一查询在不同数据版本下输出顺序一致。
7. 写在最后的实操心得
从我自己把 DuckDB 引入工作流到现在,最明显的感受是很多临时分析任务再也不用求助于重型数据平台了。以前遇到一个几百 GB 的多文件统计需求,我大概率会写一大堆 Python 脚本,然后盯着内存监控忐忑不安;现在我会先用 DuckDB 在本地快速验证、跑通逻辑,确认可行后再决定是否需要上升到更大规模的处理框架。这种“先本地后集群”的工作方式,帮我省下了不少无谓的工程噪音。
如果你只是想找一个轻量级的分析工具来替代 SQLite 和 Pandas,我确实会推荐你从 1.4.3 LTS 开始。不用害怕 SQL 基础薄弱,DuckDB 的语法足够标准,你完全可以在使用中边学边补。而且它的社区更新非常活跃,遇到问题搜一搜,大部分都能找到现成的解决方案,生态成熟度已经超出了“玩具框架”的范畴。
最后再分享一个小技巧:在 CLI 里可以用 .timer on 开启计时,查询完会显示执行耗时。这个功能在你调试性能时很实用,建议一开始就开着,养成关注执行时间的好习惯。我自己每次做完一个查询调优,都会盯着这个时间看,它比任何理论估算都更直接。
