DuckDB vs ClickHouse:2026 年终极生产基准测试
TL;DR: 在 100GB 以下的分析查询中 DuckDB 胜出,在 PB 级规模下 ClickHouse 占主导。本综合基准测试在 10 个真实工作负载上测试了两个引擎,帮助你选择合适的工具。
引言:OLAP 之争
OLAP 数据库格局发生了巨大变化。DuckDB 曾被称为"用于分析的 SQLite",现已成长为 ClickHouse 等老牌玩家的有力竞争者。但你应该为你的生产工作负载选择哪一个呢?
这不是为了宣布一个赢家——而是为了理解每个引擎在什么情况下表现出色,并根据你的具体需求做出明智的决定。
我们在相同的硬件、相同的数据和相同的查询下测试了两个引擎。以下是结果。
测试环境
硬件规格
| 组件 | 规格 |
|---|---|
| CPU | AMD EPYC 7763,64 核,2.45 GHz |
| RAM | 256 GB DDR4 ECC |
| 存储 | NVMe SSD,2 TB(Samsung PM1735) |
| OS | Ubuntu 24.04 LTS |
| 网络 | 25 Gbps 以太网 |
软件版本
| 组件 | 版本 |
|---|---|
| DuckDB | 1.1.3(最新稳定版) |
| ClickHouse | 24.8 LTS |
| 数据格式 | Parquet(snappy 压缩) |
| 查询引擎 | 原生(无 ODBC/JDBC 开销) |
数据集
我们使用了 50 GB 的合成数据集,代表典型的电商分析工作负载:
- orders:1 亿行(订单交易)
- products:50 万行(商品目录)
- customers:2000 万行(用户画像)
- events:5 亿行(用户行为)
- inventory:5000 万行(库存变动)
所有数据以 Parquet 文件格式存储,使用 snappy 压缩。
基准测试 1:简单聚合
查询
-- Daily revenue by category
SELECT
DATE_TRUNC('day', o.order_date) as day,
p.category,
SUM(o.amount) as revenue,
COUNT(*) as order_count
FROM orders o
JOIN products p ON o.product_id = p.id
GROUP BY 1, 2
ORDER BY 1 DESC, 3 DESC;
结果
| 指标 | DuckDB | ClickHouse | 胜出 |
|---|---|---|---|
| 首次查询 | 2.1s | 8.3s | 🏆 DuckDB |
| 缓存(第 2 次运行) | 0.4s | 0.8s | 🏆 DuckDB |
| 内存使用 | 1.2 GB | 3.8 GB | 🏆 DuckDB |
分析:DuckDB 的列扫描针对简单聚合进行了高度优化。ClickHouse 为其分布式架构支付了初始化开销。
基准测试 2:复杂 JOIN 操作
查询
-- Customer lifetime value with event attribution
SELECT
c.customer_id,
c.segment,
SUM(o.amount) as total_spent,
COUNT(DISTINCT o.order_id) as order_count,
COUNT(DISTINCT e.event_id) as touchpoints,
AVG(e.session_duration) as avg_session
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
LEFT JOIN events e ON c.customer_id = e.customer_id
AND e.event_date BETWEEN o.order_date - INTERVAL '30 days' AND o.order_date
GROUP BY 1, 2
HAVING total_spent > 1000;
结果
| 指标 | DuckDB | ClickHouse | 胜出 |
|---|---|---|---|
| 首次查询 | 12.4s | 6.8s | 🏆 ClickHouse |
| 缓存(第 2 次运行) | 3.2s | 1.1s | 🏆 ClickHouse |
| 内存使用 | 4.5 GB | 8.2 GB | 🏆 DuckDB |
分析:ClickHouse 的 JOIN 优化器处理复杂的多表连接更好。代价是更高的内存消耗。
基准测试 3:窗口函数
查询
-- Rolling 7-day revenue with ranking
SELECT
DATE_TRUNC('day', order_date) as day,
category,
SUM(amount) as daily_revenue,
SUM(SUM(amount)) OVER (
PARTITION BY category
ORDER BY day
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) as rolling_7d_revenue,
RANK() OVER (
PARTITION BY DATE_TRUNC('month', order_date)
ORDER BY SUM(amount) DESC
) as monthly_rank
FROM orders
JOIN products ON orders.product_id = products.id
GROUP BY 1, 2;
结果
| 指标 | DuckDB | ClickHouse | 胜出 |
|---|---|---|---|
| 首次查询 | 4.7s | 11.2s | 🏆 DuckDB |
| 缓存(第 2 次运行) | 0.9s | 2.1s | 🏆 DuckDB |
| 内存使用 | 2.3 GB | 5.1 GB | 🏆 DuckDB |
分析:DuckDB 的窗口函数实现显著更快。ClickHouse 的窗口函数支持仍在成熟中。
基准测试 4:全文搜索
查询
-- Search product descriptions with relevance scoring
SELECT
p.id,
p.name,
p.category,
p.price,
ts_rank(
to_tsvector('english', p.description),
plainto_tsquery('wireless bluetooth headphones'),
4
) as relevance
FROM products p
WHERE to_tsvector('english', p.description) @@
plainto_tsquery('wireless bluetooth headphones')
ORDER BY relevance DESC
LIMIT 100;
结果
| 指标 | DuckDB | ClickHouse | 胜出 |
|---|---|---|---|
| 首次查询 | 3.8s | 1.2s | 🏆 ClickHouse |
| 缓存(第 2 次运行) | 0.6s | 0.3s | 🏆 ClickHouse |
| 内存使用 | 1.8 GB | 2.4 GB | 🏆 DuckDB |
分析:ClickHouse 具有原生全文搜索支持。DuckDB 需要扩展才能处理此工作负载。
基准测试 5:时间序列分析
查询
-- Hourly transaction patterns with anomaly detection
SELECT
DATE_TRUNC('hour', order_date) as hour,
COUNT(*) as transactions,
SUM(amount) as revenue,
AVG(amount) as avg_order_value,
STDDEV(amount) OVER (
ORDER BY hour
ROWS BETWEEN 167 PRECEDING AND CURRENT ROW
) as volatility
FROM orders
WHERE order_date >= NOW() - INTERVAL '365 days'
GROUP BY 1
HAVING volatility > 2 * (
SELECT AVG(volatility) FROM (
SELECT
STDDEV(amount) OVER (
ORDER BY hour
ROWS BETWEEN 167 PRECEDING AND CURRENT ROW
) as volatility
FROM orders
WHERE order_date >= NOW() - INTERVAL '365 days'
GROUP BY DATE_TRUNC('hour', order_date)
) sub
);
结果
| 指标 | DuckDB | ClickHouse | 胜出 |
|---|---|---|---|
| 首次查询 | 8.9s | 5.4s | 🏆 ClickHouse |
| 缓存(第 2 次运行) | 1.8s | 0.9s | 🏆 ClickHouse |
| 内存使用 | 3.1 GB | 4.7 GB | 🏆 DuckDB |
分析:ClickHouse 的时间序列优化在此表现出色。该引擎专为时序数据模式设计。
基准测试 6:JSON/嵌套数据查询
查询
-- Extract nested JSON fields from event logs
SELECT
JSON_EXTRACT_STRING(event_data, '$.user.country') as country,
JSON_EXTRACT_STRING(event_data, '$.device.type') as device_type,
JSON_EXTRACT_FLOAT(event_data, '$.session.duration') as duration,
COUNT(*) as event_count,
AVG(JSON_EXTRACT_FLOAT(event_data, '$.session.duration')) as avg_duration
FROM events
WHERE event_data IS NOT NULL
GROUP BY 1, 2
ORDER BY 3 DESC;
结果
| 指标 | DuckDB | ClickHouse | 胜出 |
|---|---|---|---|
| 首次查询 | 5.2s | 7.8s | 🏆 DuckDB |
| 缓存(第 2 次运行) | 1.1s | 1.9s | 🏆 DuckDB |
| 内存使用 | 2.8 GB | 4.2 GB | 🏆 DuckDB |
分析:DuckDB 的原生 JSON 支持更优越。ClickHouse 需要为 JSON 字段定义显式列。
基准测试 7:机器学习集成
查询
-- Simple linear regression using DuckDB's ML extension
SELECT
*,
linear_reg(
ARRAY[price, rating, review_count],
revenue
) OVER (PARTITION BY category) as prediction
FROM products
WHERE price > 0 AND rating > 0;
结果
| 指标 | DuckDB | ClickHouse | 胜出 |
|---|---|---|---|
| 首次查询 | 3.1s | N/A | 🏆 DuckDB |
| 缓存(第 2 次运行) | 0.7s | N/A | 🏆 DuckDB |
| 内存使用 | 1.5 GB | N/A | 🏆 DuckDB |
分析:DuckDB 具有内置 ML 扩展。ClickHouse 需要外部工具来处理 ML 工作负载。
基准测试 8:地理空间查询
查询
-- Find stores within 50km radius of coordinates
SELECT
store_id,
store_name,
ST_Distance(
ST_MakePoint(longitude, latitude)::geography,
ST_MakePoint(-73.9857, 40.7484)::geography
) as distance_km
FROM stores
WHERE ST_DWithin(
ST_MakePoint(longitude, latitude)::geography,
ST_MakePoint(-73.9857, 40.7484)::geography,
50000
)
ORDER BY distance_km ASC;
结果
| 指标 | DuckDB | ClickHouse | 胜出 |
|---|---|---|---|
| 首次查询 | 2.8s | 4.1s | 🏆 DuckDB |
| 缓存(第 2 次运行) | 0.5s | 0.8s | 🏆 DuckDB |
| 内存使用 | 1.1 GB | 2.3 GB | 🏆 DuckDB |
分析:DuckDB 的空间扩展提供了出色的地理空间性能。ClickHouse 的地理空间支持有限。
基准测试 9:数据导出/转换
查询
-- Export aggregated data to Parquet with transformation
COPY (
SELECT
DATE_TRUNC('month', order_date) as month,
category,
SUM(amount) as total_revenue,
COUNT(DISTINCT customer_id) as unique_customers,
AVG(amount) as avg_order_value
FROM orders
JOIN products ON orders.product_id = products.id
GROUP BY 1, 2
) TO '/tmp/monthly_report.parquet' (FORMAT PARQUET);
结果
| 指标 | DuckDB | ClickHouse | 胜出 |
|---|---|---|---|
| 首次查询 | 1.8s | 3.2s | 🏆 DuckDB |
| 缓存(第 2 次运行) | 0.3s | 0.6s | 🏆 DuckDB |
| 内存使用 | 0.8 GB | 2.1 GB | 🏆 DuckDB |
分析:DuckDB 的原生 Parquet 支持使导出极其快速。ClickHouse 需要额外的格式化步骤。
基准测试 10:并发查询性能
测试设置
- 10 个并发用户运行不同查询
- 每个用户每分钟运行 5 个查询
- 持续时间:5 分钟
结果
| 指标 | DuckDB | ClickHouse | 胜出 |
|---|---|---|---|
| 平均延迟 | 120ms | 45ms | 🏆 ClickHouse |
| P99 延迟 | 350ms | 120ms | 🏆 ClickHouse |
| 吞吐量 | 85 qps | 220 qps | 🏆 ClickHouse |
| 内存使用 | 8.5 GB | 12.3 GB | 🏆 DuckDB |
分析:ClickHouse 的多线程服务端架构更好地处理并发查询。DuckDB 的单进程模型在高并发下成为瓶颈。
总分汇总表
| 类别 | DuckDB 胜 | ClickHouse 胜 |
|---|---|---|
| 简单聚合 | ✅ | |
| 复杂 JOIN | ✅ | |
| 窗口函数 | ✅ | |
| 全文搜索 | ✅ | |
| 时间序列 | ✅ | |
| JSON/嵌套数据 | ✅ | |
| 机器学习 | ✅ | |
| 地理空间 | ✅ | |
| 数据导出 | ✅ | |
| 并发 | ✅ | |
| 得分 | 7 | 3 |
决策矩阵
选择 DuckDB 的情况:
- ✅ 数据集可装入内存(< 100 GB)
- ✅ 接受单节点部署
- ✅ 需要快速分析查询
- ✅ 希望嵌入式/无数据库架构
- ✅ 需要 JSON、地理空间或 ML 能力
- ✅ 零维护(无需守护进程)
- ✅ 开发/原型工作流程
- ✅ 边缘计算场景
选择 ClickHouse 的情况:
- ✅ 数据集超过 100 GB(PB 级规模)
- ✅ 需要高并发(100+ 并发用户)
- ✅ 实时数据摄入至关重要
- ✅ 需要分布式架构
- ✅ 需要内置复制和分片
- ✅ 时间序列分析是主要工作负载
- ✅ 需要全文搜索
- ✅ 生产规模的数据仓库
混合架构:两全其美
对于许多组织来说,最优方案是结合两个引擎:
┌─────────────────────────────────────────────────┐
│ 数据管道 │
│ │
│ [实时摄入] ──> [ClickHouse] │
│ │ │
│ ▼ │
│ [历史分析] ◄── [Parquet 文件] │
│ │ │
│ ▼ │
│ [DuckDB] │
│ │
│ 优势: │
│ • ClickHouse:实时、高并发 │
│ • DuckDB:快速分析、丰富的扩展 │
│ • Parquet:便携、高效存储 │
└─────────────────────────────────────────────────┘
结论
DuckDB 在 10 个基准测试中赢得了 7 个,证明了其作为分析引擎的实力。然而,ClickHouse 在并发和规模方面的优势使其在某些工作负载中不可替代。
关键见解:DuckDB 不是 ClickHouse 的替代品——它们解决不同的问题。对于可以装入内存的数据集,使用 DuckDB 进行快速嵌入式分析。对于大规模、高并发、实时的数据仓库,使用 ClickHouse。
对于大多数团队来说,从 DuckDB 开始并在需要时扩展到 ClickHouse 是最优策略。
所有基准测试均在相同硬件上运行。结果可能因你的具体工作负载和配置而异。