DuckDB vs SQLite vs Pandas:列存 + 向量化为什么聚合快 100 倍 + 选型决策树

· 约 5 分钟 🦆 DuckDB SQL 工作台

DuckDB、SQLite、Pandas 表面上都能”读个文件、跑个聚合、出个结果”,但底层架构完全不同——DuckDB 是列存 + 向量化的 OLAP 引擎,SQLite 是行存 + 单行迭代的 OLTP,Pandas 是内存 DataFrame。同一个查询在三个工具上能差 100 倍。

这篇拆解三者真实差异、性能数量级、以及什么场景该选哪个。

三者的本质定位

维度SQLiteDuckDBPandas
类型OLTP 嵌入式数据库OLAP 嵌入式数据库内存 DataFrame
存储行存列存内存数组
执行单行迭代向量化(1024 行/批)向量化(NumPy)
并发单写多读单写多读单进程
适合事务、点查、单行更新聚合、JOIN、扫表行级编程、ML 集成
数据上限几百 GB单机几 TB内存大小

最关键的一句话:SQLite 优化”插入和单行查询”,DuckDB 优化”扫几亿行后聚合”,Pandas 优化”在内存里灵活操作”。

为什么 DuckDB 聚合快 100 倍

两个独立的优化叠加:

列存:IO 量减少 30 倍

行存(SQLite / Postgres / MySQL):

  • 一行的所有列连续存
  • SELECT col1, col2 FROM t 即使只要 2 列也要读全行 100 列
  • 同一列数据散布在文件各处,压缩率低

列存(DuckDB / Parquet / ClickHouse):

  • 一列的所有行连续存
  • 只读 SELECT 涉及的列,IO 减少 N 倍
  • 同列数据相似 → 字典编码 + RLE + Snappy/ZSTD 能压到行存 1/5 - 1/10

向量化:CPU 利用率提升 10 倍

传统行存执行:

for row in table:
    process(row)   # 每行一次函数调用

DuckDB 向量化:

for batch in table:           # 1024 行/批
    process_vector(batch)     # SIMD 友好
  • 函数调用开销摊薄
  • 分支预测命中率高
  • CPU 缓存友好
  • SIMD 指令一次处理 8-16 个值

CPU 利用率从 10% 提升到 80%+。

实测对比

千万行 1 GB 数据,SELECT category, SUM(amount) GROUP BY category

工具耗时倍率
SQLite30-90 秒
Pandas5-15 秒5-10×
DuckDB0.5-2 秒30-100×

DuckDB 默认多核并行,SQLite 单线程,又是一层加成。

DuckDB vs SQLite:什么时候 SQLite 更快

SQLite 不输 DuckDB 的场景

  • 单行点查(按主键查一条记录)—— 几乎一样快
  • 高频小事务插入——SQLite 写入吞吐高
  • 频繁 UPDATE——DuckDB 列存对原地更新不友好

典型架构:业务系统用 SQLite 存 OLTP 数据,定期导出 Parquet 到 DuckDB 跑分析报表。让擅长事务的做事务,擅长分析的做分析。

DuckDB vs Pandas:什么时候 Pandas 不可替代

70% 的 Pandas 用法 DuckDB 更快

场景DuckDBPandas
10 GB CSV 聚合几 GB 内存可跑需要 30-50 GB 内存
GROUP BY + SUM5-50 倍快-
多 CSV / Parquet 合并read_parquet('*.parquet') 一行for 循环 + concat
JOIN 大表hash join + parallelmerge 单线程
复杂查询SQL 清晰链式调用难读

Pandas 仍不可替代

  • 行级自定义函数——df.apply(lambda row: ...)
  • 机器学习生态——sklearn / XGBoost / PyTorch 直接吃 DataFrame
  • 可视化——matplotlib / seaborn / plotly 直接接 DataFrame
  • 小数据交互式探索——Jupyter 里 < 100 万行
  • 复杂数据清洗——大量 IF / MAP / 不规则字符串处理

最佳实践——混用

import duckdb

# DuckDB 做重活
df = duckdb.sql("""
    SELECT user_id, COUNT(*) AS cnt, SUM(amount) AS total
    FROM 'orders/*.parquet'
    WHERE date >= '2026-01-01'
    GROUP BY user_id
""").df()  # 转 DataFrame

# Pandas 做轻活 + ML
import seaborn as sns
sns.histplot(df, x='total')

把 100 GB Parquet 用 DuckDB 聚合到 10 万行 DataFrame,再用 Pandas / sklearn 训模型 / 画图。

Polars 的位置

Polars 是 Rust 写的列存 + 向量化 DataFrame 库,定位介于 Pandas 和 DuckDB 之间:

  • 比 Pandas 快 5-50 倍
  • 比 DuckDB 灵活(DataFrame API 不强制 SQL)
  • lazy execution + 查询优化(类似 Spark)

选哪个

  • 喜欢 SQL → DuckDB
  • 喜欢 DataFrame API → Polars
  • 习惯 Pandas + 不想换 → 还是 Pandas(< 1 GB 数据)

DuckDB 和 Polars 经常一起用:DuckDB 读文件 + 聚合,Polars 后续转换。

DuckDB 的内存控制

默认行为

  • memory_limit = 系统内存的 80%
  • 超限的中间结果溢写到 temp 目录(external sort + external hash)

配置项

PRAGMA memory_limit = '4GB';
PRAGMA temp_directory = '/mnt/nvme/duckdb-tmp';
PRAGMA threads = 8;
PRAGMA preserve_insertion_order = false;  -- 大数据 INSERT 用

典型内存估算

操作内存需求
简单 SELECT WHERE几乎 0(流式)
GROUP BYunique key 数 × 行宽
HASH JOIN较小表 × 1.5
窗口函数单 partition 大小
ORDER BY 大数据整个数据

实务

  • 数据 < 内存 → 默认配置即可
  • 数据 1-5 倍内存 → 配 temp_directory 到 NVMe SSD
  • 数据 > 10 倍内存 → 分批处理或换 ClickHouse / Spark

DuckDB 不适合的场景

场景为什么推荐
高频 INSERT / UPDATE 业务库单写者 + 列存不优化点更新SQLite / Postgres
高并发查询(> 100 QPS)单进程吞吐有限ClickHouse
多用户权限管理DuckDB 没有Postgres / Snowflake
持续 streaming不是设计目标ClickHouse / Flink
数据 > 10 TB单机吃力ClickHouse / Snowflake
跨团队 / 多业务共享嵌入式Snowflake / BigQuery

DuckDB 的生产用途

适合的生产场景

  1. 嵌入式分析——BI 工具后端,查询本地 Parquet
  2. 数据 pipeline 中间层——读 S3 → 转换聚合 → 写出新 Parquet
  3. 小到中型团队的分析仓库——< 1 TB,< 50 人,DuckDB + Parquet on S3 + dbt-duckdb
  4. 客户端分析——浏览器(DuckDB-Wasm)跑 ad-hoc 查询
  5. 机器学习特征工程——比 Pandas 快 10 倍,比 Spark 轻 100 倍
  6. 替代 Jupyter 里的 Pandas 大数据

MotherDuck——DuckDB 官方云托管,hybrid execution(本地 + 云混合查询),适合从单机扩展到团队协作。

选型决策树

按这个顺序判断:

负载是 OLTP(事务、单行查询、高频更新)?
  是 → SQLite(嵌入式 / 单机)/ Postgres(多机)
  否 → 继续

数据量 > 10 TB 或并发 > 100 QPS?
  是 → ClickHouse / Snowflake
  否 → 继续

需要多用户权限 / 跨团队共享?
  是 → Snowflake / BigQuery
  否 → 继续

主要场景是聚合 / JOIN / 扫表分析?
  是 → DuckDB
  否 → Pandas / Polars(行级编程为主)

实操检查清单

把现有工作流换 DuckDB 之前过一遍:

  • 真实数据跑过对比,不是看 benchmark
  • 业务负载是 OLAP 还是 OLTP,别用反了
  • 数据规模 < 单机能力(一般 < 1 TB)
  • 没有跨团队共享 / 多用户权限需求
  • 已知 Pandas / sklearn / 可视化部分继续用 Pandas,DuckDB 做重活
  • 大数据时配 temp_directory 到 NVMe
  • 不要拿 DuckDB 做 OLTP,反过来也不要
  • 不确定时先用浏览器 DuckDB-Wasm 工具跑一次看效果

DuckDB 不是 SQLite 的替代品,是补充。两者一个做事务一个做分析,配合使用最稳。

❓ 常见问题

DuckDB 和 SQLite 表面像,本质差在哪?

两者都是嵌入式单文件数据库,但定位完全相反——SQLite 是 OLTP(事务型),DuckDB 是 OLAP(分析型)架构差异:(1) 存储方式——SQLite 行存(一行的所有列连续存),DuckDB 列存(一列的所有行连续存);(2) 执行引擎——SQLite 单行迭代(一次取一行处理),DuckDB 向量化(一次处理 1024 行的批次);(3) 优化目标——SQLite 优化"插入 / 更新 / 单行查询",DuckDB 优化"扫几亿行 + 聚合 + JOIN";(4) 并发模型——SQLite 写串行(1 writer),DuckDB 也是单写多读但写入吞吐量差很多。性能数量级差异:(1) SELECT COUNT(*) FROM t WHERE x=1——百万行表,SQLite 几百毫秒,DuckDB 几毫秒(100 倍);(2) SELECT user_id, SUM(amount) FROM orders GROUP BY user_id——千万行表,SQLite 几十秒到几分钟,DuckDB 几秒(10-50 倍);(3) JOIN 大表——DuckDB 用 hash join + parallel,SQLite 用 nested loop / sort-merge,差 10-100 倍。什么时候 SQLite 更快:(1) 单行点查(按主键查一条记录)—— SQLite 几乎一样快;(2) 大量小事务插入——SQLite 写入吞吐高于 DuckDB;(3) 高频 UPDATE——DuckDB 列存对原地更新不友好。结论:(1) 业务系统数据库(CRUD、写多) → SQLite;(2) 分析查询、报表、数据科学 → DuckDB;(3) 同一份数据两种用途 → SQLite 主库 + 定期导出 Parquet 到 DuckDB 分析。

为什么 DuckDB 聚合能快 100 倍?列存 + 向量化是什么?

列存让 IO 量减少几十倍,向量化让 CPU 利用率提升 10 倍,叠加效果就是 100 倍列存优势:(1) 只读需要的列——表 100 列你只 SELECT 3 列,行存要把每行所有 100 列读进来再丢弃 97 列,列存只读那 3 列文件,IO 减少 30 倍;(2) 同列数据相似 → 压缩率高——一列全是同类型同范围数据,字典编码 + RLE + Snappy / ZSTD 能压到行存的 1/5 - 1/10;(3) 向量化友好——同列数据连续存储,CPU SIMD 指令能一次处理 8-16 个值。向量化执行:(1) 传统行存执行——for row in table: process(row),每行调用一次函数,CPU pipeline 利用率低;(2) DuckDB 向量化——一次处理 1024 行的"vector",函数调用开销摊薄,分支预测命中率高,缓存友好;(3) 效果——简单聚合(SUM / COUNT / AVG)的 CPU 利用率从 10% 提升到 80%+。典型对比(千万行 1 GB 数据,SELECT category, SUM(amount) GROUP BY category):(1) SQLite:扫描 1 GB + 哈希聚合 + 单线程 → 30-90 秒;(2) DuckDB:列裁剪只扫 200 MB + 向量化聚合 + 多核并行 → 0.5-2 秒;(3) 快 30-100 倍额外加成——DuckDB 默认多核并行(用所有 CPU 核),SQLite 单线程。实务:(1) 跑一次性的 EDA / 数据探索 → DuckDB 永远快;(2) 嵌入到生产服务做实时点查 → SQLite 更快也更轻;(3) 不要用 DuckDB 替代 SQLite 做事务型业务,反过来也不要。

DuckDB 能替代 Pandas 吗?什么场景留 Pandas?

70% 的 Pandas 用法 DuckDB 更快更省内存,但有些场景 Pandas 仍不可替代DuckDB 优于 Pandas 的场景:(1) 大数据——Pandas 必须把整个数据集加载到内存,10 GB CSV 需要 30-50 GB 内存;DuckDB 流式读 + 列裁剪 + 谓词下推,10 GB 文件几 GB 内存就能跑;(2) 聚合 / JOIN / 窗口函数——DuckDB SQL 比 Pandas .groupby().agg() / .merge() / .rolling() 快 5-50 倍;(3) 直接读 Parquet / CSV / Excel——DuckDB read_parquet('*.parquet') 一行搞定,Pandas 要循环读 + concat;(4) SQL 表达力——复杂查询(CTE、窗口、QUALIFY)SQL 比 Pandas 链式调用清晰得多。Pandas 仍优于 DuckDB:(1) 行级编程逻辑——df.apply(lambda row: ...) 这种自定义函数,DuckDB 要写 UDF 麻烦;(2) 机器学习生态——sklearn / XGBoost / PyTorch 直接吃 NumPy / DataFrame,DuckDB 输出还得转;(3) 可视化——matplotlib / seaborn / plotly 直接接 DataFrame;(4) 小数据交互式探索——Jupyter 里 < 100 万行数据,Pandas 的 .head() / .describe() 习惯成本最低;(5) 复杂数据清洗——大量 IF / MAP / 不规则字符串处理,Pandas 链式比 SQL 顺手。最佳实践——混用:(1) DuckDB 做重活——读文件、JOIN、聚合、过滤,输出小结果集;(2) Pandas 做轻活 + ML——duckdb.sql(...).df() 把结果转 DataFrame 后接 sklearn / 画图;(3) 典型流程——从 100 GB Parquet 用 DuckDB SQL 聚合到 10 万行的 DataFrame,再用 Pandas / sklearn 训模型 / 画图。Polars 的位置:DataFrame API + 列存 + 向量化(Rust 写的);如果就要 DataFrame API 不要 SQL,Polars 是 Pandas 的现代替代;DuckDB 和 Polars 各有优势,常一起用。

DuckDB 适合做实时数据处理 / OLTP 吗?

不适合DuckDB 的设计取舍:(1) 优化分析查询——读多 / 批量写 / 列裁剪 / 聚合;(2) 不优化高频写——单事务插入 1000 行很快,但 1000 个并发事务每个插入 1 行就慢;(3) 不优化点查更新——UPDATE t SET x=? WHERE id=? 这种行级更新,列存格式要重写整个 row group,性能差;(4) 没有行级锁——并发写者排队,不像 Postgres / MySQL 多行并发。实测瓶颈:(1) 小事务并发写——10 个并发线程各 1000 个 INSERT,DuckDB 几秒到几十秒;同样负载 SQLite 1-3 秒,Postgres 0.5 秒以下;(2) 频繁 UPDATE——业务系统每次操作改一两行,DuckDB 不擅长;(3) 跨进程并发——DuckDB 默认是单进程嵌入式,多进程访问要用专门的 server 模式或 MotherDuck。适合 DuckDB 的"实时":(1) 流式追加 + 周期性聚合——每分钟批量 INSERT 几千行,每小时 / 每天跑聚合查询——OK;(2) 数据分析后台——产品经理 / 分析师跑 ad-hoc SQL,每个查询几毫秒到几秒返回——完美;(3) 嵌入到分析服务——例如 BI 工具 / 数据可视化 backend——OK。实际架构推荐:(1) 业务库 Postgres / SQLite/ MySQL 接收 OLTP 流量;(2) CDC 同步 到 DuckDB / ClickHouse / Snowflake 做分析;(3) DuckDB 作为分析层 跑各种聚合和报表——让擅长事务的做事务,擅长分析的做分析

DuckDB 的内存控制怎么做?数据比内存大能跑吗?

支持 spill to disk(溢写到磁盘),但配置得当才不爆默认行为:(1) DuckDB 默认 memory_limit = 系统内存的 80%;(2) 超过限制的中间结果会溢写到 temp 目录(默认 OS 临时目录);(3) 溢写算法是 external sort + external hash —— 比 Spark 简单但够用。配置项:(1) PRAGMA memory_limit='4GB' 限制内存;(2) PRAGMA temp_directory='/path/to/fast/disk' 指定溢写目录(建议 NVMe SSD);(3) PRAGMA threads=8 控制并行度(默认所有核);(4) PRAGMA preserve_insertion_order=false 大数据 INSERT 时关掉,更快。典型场景的内存估算:(1) 简单聚合 GROUP BY——内存 ≈ unique key 数 × 行宽;100 万 unique key × 100 字节 = 100 MB;(2) HASH JOIN——内存 ≈ 较小表 × 1.5;10 GB 小表需要 15 GB 内存或溢写;(3) 窗口函数——按 partition 处理,内存 ≈ 单 partition 大小;(4) 简单 SELECT WHERE——流式处理,内存几乎不增。爆内存的诱因:(1) JOIN 时把大表当 build side(DuckDB 通常自动选小表,但有时统计不准);(2) ORDER BY 大量数据;(3) 复杂窗口函数 + 大 partition;(4) array_agg / list_agg 在大 GROUP BY 上。实务:(1) 数据 < 内存 → 一切默认即可;(2) 数据 = 1-5 倍内存 → 配 temp_directory 到快 SSD;(3) 数据 > 10 倍内存 → 考虑分批处理或换 ClickHouse / Spark;(4) 极少需要手动调 —— DuckDB 的查询规划器自动判断。

DuckDB 在生产环境能怎么用?什么情况下选 ClickHouse / Snowflake?

DuckDB 适合"嵌入式分析"和"中等规模数据仓",超大规模换 ClickHouse / SnowflakeDuckDB 适合的生产场景:(1) 嵌入式分析——BI 工具 / 仪表盘后端,查询本地 Parquet 文件;(2) 数据 pipeline 中间层——读 S3 Parquet → 转换聚合 → 写出新 Parquet;(3) 小到中型团队的分析仓库——数据量 < 1 TB,团队 < 50 人,DuckDB + Parquet on S3 + dbt-duckdb 完整可用;(4) 客户端分析——浏览器(DuckDB-Wasm)跑 ad-hoc 查询;(5) 机器学习特征工程——比 Pandas 快 10 倍,比 Spark 轻 100 倍;(6) 替代 jupyter 里的 Pandas 大数据计算——同样的代码,DuckDB 处理 100 GB CSV 不爆内存。MotherDuck——DuckDB 的官方云托管,提供"hybrid execution"——本地 + 云端混合查询,适合从 DuckDB 单机扩展到团队协作。DuckDB 的天花板:(1) 数据量 > 10 TB——单机磁盘 / 内存吃力;(2) 并发查询 > 100 QPS——单进程吞吐有限;(3) 多用户权限管理——DuckDB 没有;(4) 持续 streaming——不是设计目标。这种情况选 ClickHouse:(1) PB 级数据——分布式列存;(2) 高并发分析查询(在线 BI 服务)——ClickHouse 单查询慢点但并发高;(3) 实时数据接入(Kafka → CH)—— ClickHouse 流式 ingest 强;(4) 物化视图加速这种情况选 Snowflake / BigQuery:(1) 企业级 + 团队权限——细粒度 RBAC;(2) 跨团队 / 跨业务数据分享;(3) 不想运维——按查询计费,零运维;(4) 生态集成(Tableau / Looker / Fivetran 等)。实操选型:(1) 单人 / 小团队,数据 < 1 TB → DuckDB;(2) 中等公司,数据 1-100 TB → DuckDB / ClickHouse 二选一,看是否需要高并发;(3) 大企业,PB 级 + 多团队 → Snowflake / BigQuery / Databricks。

🦆 打开 DuckDB SQL 工作台 拖入 Parquet/CSV/JSON/Excel→SQL 查询/修改·多文件 JOIN·导出 Parquet/CSV/JSON/Excel·DuckDB-Wasm 浏览器内运行

📖 同一工具的其他教程