duckdb.connect() 默认创建内存数据库,不持久化;需显式指定文件路径如 duckdb.connect("data.duckdb") 才能持久化,且推荐用 df.to_arrow() + register() 加载大 pandas 数据以提升性能。

duckdb.connect() 默认是内存数据库,不持久化
很多人以为 duckdb.connect() 会自动读写当前目录下的 my.db 文件,其实它默认创建的是纯内存数据库——进程一关,数据全丢。这不是 bug,是设计如此。
实操建议:
- 要持久化,必须显式传入文件路径:
duckdb.connect("data.duckdb") - 路径不存在时会自动创建;存在时直接打开并复用已有表结构和数据
- 如果路径写成
":memory:"(显式声明),效果和不传参数一样,仍是内存模式 - 注意文件权限:Linux/macOS 下若路径在只读目录,会静默失败或报
IOError: Unable to open file
df.to_arrow() → register() 是高效加载 Pandas 的关键路径
直接用 con.execute("INSERT INTO t SELECT * FROM df") 看似自然,但对大 df 极慢——DuckDB 会把 DataFrame 全部转成 Python 对象再逐行插入。
正确做法是绕过 Python 层,走 Arrow 零拷贝通道:
立即学习“Python免费学习笔记(深入)”;
- 先调
df.to_arrow()得到pyarrow.Table - 再用
con.register("t", arrow_table)注册为临时表 - 后续所有 SQL 查询都可直接引用
t,性能接近原生读 Parquet - 别注册同名表两次,否则会报
RuntimeError: Catalog Error: Table with name t already exists
SQL 执行后不 fetch 就没结果,且 cursor 不自动关闭
DuckDB 的 con.execute() 返回的是 duckdb.DuckDBPyConnection 自身,不是 cursor 对象;真正执行和取数靠链式调用,容易漏掉最后一步。
常见错误现象:
-
con.execute("SELECT count(*) FROM t")运行完屏幕没输出,也没报错——其实结果已准备好,但没取 - 要用
.fetchall()或.fetchnumpy()显式拉取,比如:con.execute("SELECT * FROM t").fetchnumpy() - 长时间运行的脚本里,反复
execute却不fetch,可能累积内存(尤其大结果集) - 连接不用时建议手动
con.close(),虽然 Python GC 通常能回收,但嵌入式场景下资源释放更可控
WHERE 条件里用 Python 变量得用参数化,别拼字符串
写 con.execute(f"SELECT * FROM t WHERE id = {user_id}") 看起来快,但有 SQL 注入风险,且 DuckDB 对非字面量类型推断不稳定(比如 None 会被当成字符串)。
应该用问号占位符 + 参数元组:
con.execute("SELECT * FROM t WHERE id = ?", [user_id])- 支持多参数:
con.execute("WHERE a > ? AND b IN (?, ?)", [10, "x", "y"]) - 参数类型由 Python 值自动映射(
int→INTEGER,str→VARCHAR),比手动 cast 更稳 - 如果参数是
None,DuckDB 正确转为 SQLNULL;而字符串拼接里"None"就真成了字符串字面量
嵌入式分析里最常被忽略的,其实是连接生命周期和数据加载路径的选择——不是所有 DataFrame 都适合注册成表,小数据直接用 df 当 SQL 表名也行(DuckDB 支持),但一旦超过几百万行,Arrow 路径就是分水岭。










