Featured image of post DuckDB vs SQLite 百万行查询速度对比:实测到底快多少倍?

DuckDB vs SQLite 百万行查询速度对比:实测到底快多少倍?

用 100 万行电商数据真实对比 DuckDB 与 SQLite 的分析查询性能。涵盖 10 类典型查询,包含完整 SQL 代码和运行时间数据,助你在大数据和嵌入式数据库之间做出正确选择。

问题:SQLite 撑不住分析查询了

小李运营的电商网站每天产生约 50 万条订单数据,一直用 SQLite 存着。最近老板要看「按品类统计的季度销售趋势」,小李写好 SQL 一跑——等了 30 秒还没出结果

SQLite 是一款优秀的嵌入式 OLTP 数据库,在单行插入、简单主键查询上表现出色。但当你开始对它跑 GROUP BY、窗口函数、多表聚合 这类分析查询时,情况就不同了。

DuckDB 恰好填补了这个空白——它同样是嵌入式数据库(无需服务器),但专门为 OLAP(在线分析处理)场景设计。

问题是:DuckDB 比 SQLite 快多少?什么时候该换用 DuckDB?

本文用 100 万行真实电商数据进行实测,给出可量化的答案。


测试环境与数据

硬件/软件

项目规格
CPUAMD EPYC (4 vCPU)
内存8 GB
存储NVMe SSD
OSUbuntu 22.04
DuckDBv1.5.2
SQLite3.45.1

测试数据

使用一个包含 100 万行 的电商订单数据集,结构如下:

字段类型说明
idINTEGER主键
categoryVARCHAR商品品类(6 类)
product_nameVARCHAR商品名(10000 种)
priceDOUBLE价格 ($5–$505)
quantityINTEGER数量 (1–10)
discountDOUBLE折扣 ($1–$101)
order_dateDATE2025 年随机日期
regionVARCHAR区域 (CN/US)
user_idVARCHAR用户 (50000 人)

生成数据(DuckDB 版)

-- 在 DuckDB 中生成 100 万行测试数据
COPY (
  SELECT 
    range + 1 AS id,
    CASE WHEN random() < 0.3 THEN 'electronics'
         WHEN random() < 0.5 THEN 'clothing'
         WHEN random() < 0.65 THEN 'home'
         WHEN random() < 0.78 THEN 'books'
         WHEN random() < 0.88 THEN 'sports'
         ELSE 'food' END AS category,
    'product_' || (range % 10000 + 1) AS product_name,
    ROUND(random() * 500 + 5, 2) AS price,
    (random() * 10 + 1)::INT AS quantity,
    ROUND(random() * 100 + 1, 2) AS discount,
    DATE '2025-01-01' + INTERVAL (random() * 364) DAY AS order_date,
    CASE WHEN random() < 0.5 THEN 'CN' ELSE 'US' END AS region,
    'user_' || (range % 50000 + 1) AS user_id
  FROM range(1000000)
) TO 'ecommerce_1m.csv' (HEADER, DELIMITER ',');

10 个典型查询的对比

测试方法

  • DuckDB:直接从 CSV 查询(read_csv_auto),零 ETL
  • SQLite:先 .import CSV 到表,再查询
  • 每个查询跑多次取有代表性的时间
  • 两数据库查询完全相同(语法尽量兼容)

测试 SQL 代码

DuckDB 版:

-- DuckDB:直接从 CSV 加载(零 ETL)
CREATE TABLE sales AS 
  SELECT * FROM read_csv_auto('ecommerce_1m.csv');

-- Q1: 简单计数
SELECT COUNT(*) FROM sales;

-- Q2: 总收入计算
SELECT SUM(price * quantity) AS total_revenue FROM sales;

-- Q3: 按品类分组聚合
SELECT category, COUNT(*) AS orders, 
       SUM(price * quantity) AS revenue, AVG(price) AS avg_price
FROM sales GROUP BY category ORDER BY revenue DESC;

-- Q4: 日期范围过滤
SELECT COUNT(*), SUM(price * quantity) 
FROM sales WHERE order_date BETWEEN '2025-06-01' AND '2025-08-31';

-- Q5: 多维度分组
SELECT region, category, COUNT(*) AS cnt, SUM(price * quantity) AS revenue
FROM sales GROUP BY region, category ORDER BY revenue DESC;

-- Q6: 窗口函数 - 月度累计
SELECT strftime(order_date, '%Y-%m') AS month, 
       SUM(price * quantity) AS monthly_revenue,
       SUM(SUM(price * quantity)) OVER (ORDER BY strftime(order_date, '%Y-%m')) AS running_total
FROM sales GROUP BY month ORDER BY month;

-- Q7: Top 10 商品
SELECT product_name, SUM(price * quantity) AS revenue, COUNT(*) AS orders
FROM sales GROUP BY product_name ORDER BY revenue DESC LIMIT 10;

-- Q8: 按区域统计客单价
SELECT region, AVG(price * quantity) AS avg_order_value, 
       COUNT(*) AS orders, SUM(price * quantity) AS total_revenue
FROM sales GROUP BY region;

-- Q9: 高频用户(下单 > 5 次)
SELECT user_id, COUNT(*) AS order_count, SUM(price * quantity) AS total_spent
FROM sales GROUP BY user_id 
HAVING COUNT(*) > 5 ORDER BY total_spent DESC LIMIT 20;

-- Q10: 条件聚合
SELECT 
  SUM(CASE WHEN price > 200 THEN 1 ELSE 0 END) AS expensive_orders,
  SUM(CASE WHEN discount > 50 THEN 1 ELSE 0 END) AS high_discount_orders,
  AVG(CASE WHEN region = 'CN' THEN price ELSE NULL END) AS cn_avg_price,
  AVG(CASE WHEN region = 'US' THEN price ELSE NULL END) AS us_avg_price
FROM sales;

SQLite 版(语法兼容):

-- SQLite:需先导入 CSV
.mode csv
.import ecommerce_1m.csv sales

-- 查询与 DuckDB 相同,唯一区别:
-- DuckDB 用 strftime() 而 SQLite 的 strftime() 参数顺序略有不同,但功能等价

实测结果

#查询类型DuckDBSQLite加速比
Q1COUNT(*)0.004s0.035s8.7x
Q2SUM 总收入0.005s0.408s81.6x
Q3GROUP BY 品类0.020s2.275s113.8x
Q4日期过滤0.006s0.413s68.8x
Q5多维度 GROUP BY0.020s4.185s209.3x
Q6窗口函数0.094s1.555s16.5x
Q7Top 10 商品0.041s1.473s35.9x
Q8客单价统计0.010s1.687s168.7x
Q9HAVING 子句0.056s2.323s41.5x
Q10条件聚合0.043s1.303s30.3x

关键发现:在涉及全表扫描+聚合的查询(SUM、GROUP BY)上,DuckDB 比 SQLite 快 80–200 倍。在简单 COUNT 上差距最小(8.7 倍),因为 SQLite 有 B-Tree 索引可以加速。


为什么 DuckDB 这么快?

1. 列式存储 vs 行式存储

DuckDB(列式)SQLite(行式)
读取方式只读需要的列读整行再丢弃不需要的列
压缩率高(同列数据类型一致)
缓存效率列数据连续存储,CPU cache 友好行数据分散

举例:Q2 只要求 pricequantity 两列,DuckDB 只读取这 2 列,而 SQLite 需要读取所有 9 列。磁盘 I/O 相差 4.5 倍。

2. 向量化执行

DuckDB 按 1024 行一批 批量处理数据,充分利用 CPU 的 SIMD 指令。SQLite 逐行处理,有大量函数调用开销。

3. 多线程并行

DuckDB 在所有查询中自动使用多核,而 SQLite 默认单线程(SQLite 的并行写入也不安全)。

4. 零拷贝读取

DuckDB 可以直接查询 CSV/Parquet 文件,不需要导入阶段——这在处理一次性分析时节省了大量时间。


什么时候该用 SQLite?什么时候该换 DuckDB?

✅ 继续用 SQLite 的场景

  • Web 应用后端:需要低延迟的单行插入/更新
  • 移动端/桌面 App:SQLite 是嵌入式的首选,安装包仅有几百 KB
  • 事务密集型:多个并发写入,ACID 保证
  • 数据量 < 10 万行:此时差距不明显

✅ 换 DuckDB 的场景

  • 分析报表生成:GROUP BY、窗口函数、复杂的聚合查询
  • 大数据量探索:100 万行以上数据,快速获取洞察
  • ETL 管道:读取 CSV/Parquet/JSON,清洗、转换后输出
  • 批量处理:不需要低延迟,但要高吞吐

黄金法则

OLTP 选 SQLite,OLAP 选 DuckDB。

如果数据既需要事务写入又需要分析查询——可以考虑在 SQLite 中写入,用 DuckDB 的 sqlite 扩展直接查询 SQLite 数据库:

-- DuckDB 直接查询 SQLite 数据库!
INSTALL sqlite;
LOAD sqlite;
SELECT category, SUM(price * quantity) AS revenue
FROM sqlite_scan('myapp.db', 'orders')
GROUP BY category ORDER BY revenue DESC;

实际迁移案例

小李最终这样解决问题:

  1. 保留 SQLite 作为写入数据库:电商系统继续写入 SQLite
  2. DuckDB 做分析层:每天凌晨用 DuckDB 读取 SQLite 数据生成报表
  3. 结果:原来跑 30 秒的月度销售报表,降到 0.1 秒
# Python 示例:用 DuckDB 分析 SQLite 数据
import duckdb

con = duckdb.connect()
con.execute("INSTALL sqlite; LOAD sqlite;")

# 直接分析 SQLite 数据,无需导出
result = con.execute("""
    SELECT strftime(order_date, '%Y-%m') AS month,
           category,
           SUM(price * quantity) AS revenue
    FROM sqlite_scan('ecommerce.db', 'orders')
    GROUP BY month, category
    ORDER BY month, revenue DESC
""").fetchdf()

print(result)

总结

对比维度DuckDBSQLite
设计目标OLAP 分析OLTP 事务
100 万行 GROUP BY0.02s2.28s
多维度聚合0.02s4.19s
窗口函数0.09s1.56s
安装大小~50MB~600KB
并发写入不支持✅ 支持
直接查 CSV✅ 原生支持❌ 需导入

一句话总结:如果你的数据超过 10 万行,且查询涉及聚合分析——DuckDB 是比 SQLite 好 10–200 倍的选择。两者不是替代关系,而是互补——SQLite 管写入,DuckDB 管分析,各取所长。


架构图

推荐阅读

📺 Watch video tutorials → DuckDB Lab YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计