Python 嵌入式 OLAP 数据库:DuckDB
DuckDB 是一个近几年非常火的 嵌入式 OLAP 数据库,你可以把它看作是 “数据分析领域的 SQLite”。它特别适合在 单机/笔记本环境下做大规模数据分析。
🦆 DuckDB 速览
📌 核心特点
- 嵌入式:像 SQLite 一样,无需单独的服务进程,直接嵌入到应用或脚本里。
- OLAP 优化:面向分析型场景(OLAP),列存储、向量化执行。
- 零配置:下载即用,不需要复杂的配置。
- 超快性能:在大表 JOIN、聚合、窗口函数上表现接近甚至优于 Spark/Pandas。
- 兼容性强:支持 SQL 标准,能直接读写 Parquet、CSV、JSON,并且和 pandas、Arrow 无缝集成。
🔗 与常见工具对比
| 特性 | DuckDB | SQLite | Pandas | Spark |
|---|---|---|---|---|
| 定位 | 嵌入式 OLAP 数据库 | 嵌入式事务型数据库 | 内存数据分析 | 分布式大数据计算 |
| 存储格式 | 列存储 | 行存储 | 内存 | 分布式文件系统 |
| 性能优化 | 向量化执行,SIMD 加速 | 简单执行引擎 | 单线程操作为主 | 分布式调度优化 |
| 数据规模 | GB ~ TB 级别(单机内存+磁盘) | MB ~ GB | 内存受限 | TB ~ PB(集群) |
| 接口 | SQL / Python / R | SQL / Python | Python API | SQL / PySpark / Scala |
| 典型场景 | 本地大数据分析、交互式查询、ETL | 嵌入式事务、配置存储 | 数据探索、小规模分析 | 企业级大规模数据仓库 |
🛠️ 使用示例(Python)
import duckdb
import pandas as pd
# 创建一个内存数据库
con = duckdb.connect()
# 从 CSV 文件直接查询
df = con.execute("SELECT * FROM 'data.csv' WHERE amount > 100").df()
# 与 pandas 无缝结合
pdf = pd.DataFrame({
"user": ["a", "b", "c"],
"score": [10, 20, 30]
})
result = con.execute("SELECT user, score*2 AS double_score FROM pdf").df()
print(result)
-
con.execute() 用于执行 SQL 查询。执行结果不是立即返回数据,而是返回一个结果对象。
-
你可以使用 fetchdf()、fetchall()、fetchone() 或 fetchmany() 方法从结果中提取数据。
- fetchdf():返回查询结果为 pandas DataFrame。可以进一步设置 columns、index 等参数来控制返回的列。
- fetchall():返回查询结果的 所有行,以 列表 形式返回。
- fetchone():返回查询结果的 第一行,以 元组 形式返回。
- fetchmany(n):返回查询结果的 前 n 行,以 列表 形式返回。
-
适用于执行任意 SQL 查询(包括 SELECT、INSERT、UPDATE 等),并且支持参数化查询以提高安全性。
# 创建连接
con = duckdb.connect()
# 执行插入操作
con.execute("INSERT INTO my_table VALUES (1, 'sample', 10.5)")
# 执行更新操作
con.execute("UPDATE my_table SET column_name = 'new_value' WHERE column_name = 'old_value'")
SELECT * FROM read_csv_auto('path/to/your/file.csv');
CREATE TABLE netflix_top10 AS SELECT * FROM read_csv_auto('path/to/your/file.csv');
CREATE TABLE netflix AS SELECT * FROM read_parquet('s3://us-prd-motherduck-open-datasets/netflix/netflix_daily_top_10.parquet');
FROM netflix;
duckdb.sql("""
SELECT station, count(*) AS num_services
FROM train_services
GROUP BY ALL
ORDER BY num_services DESC
LIMIT 3;
""")
Basic API Usage
import duckdb
r1 = duckdb.sql("SELECT 42 AS i")
duckdb.sql("SELECT i * 2 AS k FROM r1").show()
Data Input
import duckdb
duckdb.read_csv("example.csv") # read a CSV file into a Relation
duckdb.read_parquet("example.parquet") # read a Parquet file into a Relation
duckdb.read_json("example.json") # read a JSON file into a Relation
duckdb.sql("SELECT * FROM 'example.csv'") # directly query a CSV file
duckdb.sql("SELECT * FROM 'example.parquet'") # directly query a Parquet file
duckdb.sql("SELECT * FROM 'example.json'") # directly query a JSON file
DataFrames
Pandas
import duckdb
import pandas as pd
pandas_df = pd.DataFrame({"a": [42]})
duckdb.sql("SELECT * FROM pandas_df")
Polars
import duckdb
import polars as pl
polars_df = pl.DataFrame({"a": [42]})
duckdb.sql("SELECT * FROM polars_df")
PyArrow
import duckdb
import pyarrow as pa
arrow_table = pa.Table.from_pydict({"a": [42]})
duckdb.sql("SELECT * FROM arrow_table")
Result Conversion
import duckdb
duckdb.sql("SELECT 42").fetchall() # Python objects
duckdb.sql("SELECT 42").df() # Pandas DataFrame
duckdb.sql("SELECT 42").pl() # Polars DataFrame
duckdb.sql("SELECT 42").arrow() # Arrow Table
duckdb.sql("SELECT 42").fetchnumpy() # NumPy Arrays
Writing Data to Disk
import duckdb
duckdb.sql("SELECT 42").write_parquet("out.parquet") # Write to a Parquet file
duckdb.sql("SELECT 42").write_csv("out.csv") # Write to a CSV file
duckdb.sql("COPY (SELECT 42) TO 'out.parquet'") # Copy to a Parquet file
Connection Options
Using an In-Memory Database
import duckdb
con = duckdb.connect()
con.sql("SELECT 42 AS x").show()
Persistent Storage
The duckdb.connect(dbname) creates a connection to a persistent database. Any data written to that connection will be persisted, and can be reloaded by reconnecting to the same file, both from Python and from other DuckDB clients.
import duckdb
# create a connection to a file called 'file.db'
con = duckdb.connect("file.db")
# create a table and load data into it
con.sql("CREATE TABLE test (i INTEGER)")
con.sql("INSERT INTO test VALUES (42)")
# query the table
con.table("test").show()
# explicitly close the connection
con.close()
# Note: connections also closed implicitly when they go out of scope
Configuration
import duckdb
con = duckdb.connect(config = {'threads': 1})
https://duckdb.org/docs/stable/clients/python/overview.html
📊 适用场景
- 本地大数据探索(几 GB ~ 几百 GB 的 CSV/Parquet,直接查)
- 快速原型分析(SQL + pandas 混合工作流)
- 数据科学工作流加速(替代部分 Pandas 运算,极大提速)
- BI/ETL 工具的嵌入式分析引擎(比如直接在应用内做 SQL 分析)
👉 总结一句:
DuckDB = 本地单机版的“分析型数据库”,性能媲美大数据框架,但使用体验比 Pandas 更轻便。
比对大文件操作
import os
import time
import psutil
import pandas as pd
import duckdb
import numpy as np
import pathlib
# ============ 1. 生成大 CSV ============ #
file = "big.csv"
Path = pathlib.Path
p = Path("big.csv")
p.unlink(missing_ok=True)
if not os.path.exists(file):
print("生成大 CSV 文件中...")
rows = 10_000_000 # 1000 万行,约 1GB 大小(取决于列数和类型)
cols = {
"id": np.arange(rows),
"value1": np.random.randint(0, 1000000, size=rows),
"value2": np.random.rand(rows),
"category1": np.random.choice(["global", "postal", "sending", "America", "services", "response", "announced", "duty-free", "exemption", "low-value"], size=rows),
"category2": np.random.choice(["shares", "return", "growth", "further", "staying", "Chinese", "cautious", "economic", "Insurance", "portfolio"], size=rows),
"category3": np.random.choice(["folk", "goods", "poorly", "mainly", "bought", "younger", "designed", "products", "consumers", "fashionable"], size=rows)
}
df = pd.DataFrame(cols)
df.to_csv(file, index=False)
print(f"完成: {file}, 大小约 {os.path.getsize(file) / (1024 ** 3):.2f} GB")
# ============ 2. Benchmark 函数 ============ #
def benchmark_pandas(file):
start = time.time()
process = psutil.Process(os.getpid())
mem_before = process.memory_info().rss / (1024 ** 2)
df = pd.read_csv(file)
mem_after = process.memory_info().rss / (1024 ** 2)
end = time.time()
print(f"[Pandas] 耗时: {end - start:.2f} 秒, "
f"内存增加: {mem_after - mem_before:.2f} MB")
def benchmark_duckdb(file):
start = time.time()
process = psutil.Process(os.getpid())
mem_before = process.memory_info().rss / (1024 ** 2)
con = duckdb.connect()
# df = con.execute(f"SELECT * FROM read_csv_auto('{file}')").df()
df = con.execute(f"SELECT * FROM read_csv_auto('{file}')") # 不转 DataFrame,则几乎不占内存
mem_after = process.memory_info().rss / (1024 ** 2)
end = time.time()
print(f"[DuckDB] 耗时: {end - start:.2f} 秒, "
f"内存增加: {mem_after - mem_before:.2f} MB")
# ============ 3. 执行对比 ============ #
benchmark_pandas(file)
benchmark_duckdb(file)
生成大 CSV 文件中...
完成: big.csv, 大小约 0.55 GB
[Pandas] 耗时: 2.44 秒, 内存增加: 278.27 MB
[DuckDB] 耗时: 0.04 秒, 内存增加: 46.86 MB
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)