1. 项目背景与测试环境配置
最近在分析金融数据时遇到了一个典型问题:当处理千万级股票交易记录时,传统关系型数据库的查询性能开始捉襟见肘。这促使我对比测试了DuckDB和MySQL在相同硬件环境下处理超大规模数据集的性能差异。测试环境选用了一台配备24核CPU和64GB内存的工作站,确保两种数据库都能充分发挥硬件性能。
测试使用的数据集包含三个关键表:
- 主表stock_daily(1415万行,1.15GB CSV):存储股票每日交易数据
- 维度表stock_base(5362行):股票基本信息
- 维度表stock_company(5146行):上市公司详情
所有测试都在禁用查询缓存的情况下进行,确保每次查询都是真实的性能反映。MySQL 8.0.12采用默认配置,仅针对测试表建立了必要的索引(主键和交易日期索引)。DuckDB 1.5.1则直接读取CSV文件,不进行任何预处理。
重要提示:测试使用机械硬盘而非SSD,这放大了I/O性能差异。实际生产环境中若使用SSD,性能差距会有所缩小。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础查询性能对比
2.1 简单聚合查询
最基本的count(*)查询就显示出巨大差异:
sql复制-- MySQL耗时8.038秒
SELECT COUNT(*) FROM stock_daily;
-- DuckDB耗时0.452秒
SELECT COUNT(*) FROM 'stock_daily.csv';
DuckDB仅用MySQL 5.6%的时间就完成了全表计数。这种优势在分组聚合中更加明显:
sql复制-- MySQL耗时2.627秒
SELECT ts_code, COUNT(*) FROM stock_daily GROUP BY ts_code;
-- DuckDB耗时0.478秒
SELECT ts_code, COUNT(*) FROM 'stock_daily.csv' GROUP BY ts_code;
2.2 排序查询性能
当引入ORDER BY子句后,MySQL的耗时急剧上升:
sql复制-- MySQL耗时13.16秒
SELECT ts_code, COUNT(*) AS days
FROM stock_daily
GROUP BY ts_code
ORDER BY days;
-- DuckDB耗时0.495秒
SELECT ts_code, COUNT(*) AS days
FROM 'stock_daily.csv'
GROUP BY ts_code
ORDER BY days;
这里DuckDB的速度达到MySQL的26倍。分析执行计划发现,MySQL需要将临时表写入磁盘再进行排序,而DuckDB的向量化执行引擎可以完全在内存中处理。
3. 复杂查询场景对比
3.1 条件过滤查询
测试一个典型的分析场景:统计特定交易日上涨股票数量
sql复制-- MySQL耗时4.194秒
SELECT COUNT(*) AS up
FROM stock_daily
WHERE trade_date='2020-05-07 00:00:00'
AND close > open;
-- DuckDB耗时0.511秒
SELECT COUNT(*) AS up
FROM read_csv('stock_daily.csv', auto_detect=true)
WHERE trade_date = DATE '2020-05-07'
AND close > open;
虽然MySQL使用了trade_date上的索引,但DuckDB的列式存储和向量化处理仍然快了8倍。
3.2 多表关联查询
加入行业分类信息后,性能差异更加显著:
sql复制-- MySQL耗时4.705秒
SELECT b.industry, COUNT(*) AS up
FROM stock_daily a
LEFT JOIN stock_base b ON a.ts_code = b.ts_code
WHERE a.trade_date='2020-05-07 00:00:00'
AND a.close > a.open
GROUP BY b.industry
ORDER BY up DESC;
-- DuckDB耗时0.596秒
SELECT b.industry, COUNT(*) AS up
FROM read_csv('stock_daily.csv', auto_detect=true) a
LEFT JOIN read_csv('stock_base.csv', auto_detect=true) b
ON a.ts_code = b.ts_code
WHERE a.trade_date = DATE '2020-05-07'
AND a.close > a.open
GROUP BY b.industry
ORDER BY up DESC;
3.3 多层关联查询
当关联第三个表时,DuckDB的优势继续保持:
sql复制-- MySQL耗时4.705秒
SELECT c.exchange, b.industry, COUNT(*) AS up
FROM stock_daily a
LEFT JOIN stock_base b ON a.ts_code = b.ts_code
LEFT JOIN stock_company c ON a.ts_code = c.ts_code
WHERE a.trade_date='2020-05-07 00:00:00'
AND a.close > a.open
GROUP BY c.exchange, b.industry
ORDER BY up DESC;
-- DuckDB耗时0.883秒
SELECT c.exchange, b.industry, COUNT(*) AS up
FROM read_csv('stock_daily.csv', auto_detect=true) a
LEFT JOIN read_csv('stock_base.csv', auto_detect=true) b
ON a.ts_code = b.ts_code
LEFT JOIN read_csv('stock_company.csv', auto_detect=true) c
ON a.ts_code = c.ts_code
WHERE a.trade_date = DATE '2020-05-07'
AND a.close > a.open
GROUP BY c.exchange, b.industry
ORDER BY up DESC;
4. 性能优化技巧
4.1 物化视图加速
DuckDB支持创建物化视图进一步提升查询速度:
sql复制-- 创建物化视图
CREATE TABLE view_stock_daily AS
SELECT * FROM 'stock_daily.csv';
-- 查询速度可再提升30-50%
SELECT * FROM view_stock_daily;
4.2 文件格式选择
除了CSV,DuckDB对Parquet格式的支持更好:
sql复制-- 将CSV转换为Parquet
COPY (SELECT * FROM 'stock_daily.csv')
TO 'stock_daily.parquet' (FORMAT PARQUET);
-- Parquet查询通常比CSV快2-3倍
SELECT COUNT(*) FROM 'stock_daily.parquet';
4.3 内存配置优化
对于超大数据集,调整DuckDB内存参数很关键:
sql复制-- 设置内存限制为32GB
SET memory_limit='32GB';
-- 启用并行处理(使用所有CPU核心)
SET threads TO 24;
5. 适用场景分析
5.1 DuckDB优势场景
- 数据分析师本地探索性分析
- 需要快速处理CSV/Parquet文件的场景
- 复杂聚合查询和即席查询(ad-hoc)
- 内存充足的中小型数据集(百GB级别以下)
5.2 MySQL优势场景
- 高并发事务处理(OLTP)
- 需要严格ACID保证的场景
- 已有成熟MySQL生态的环境
- 需要行级锁定的应用
6. 实际应用建议
对于金融数据分析这类场景,我推荐混合使用两种数据库:
- 使用DuckDB进行数据清洗和初步分析
- 将处理后的结果导入MySQL供应用程序查询
- 对超大规模历史数据分析保持CSV/Parquet格式,用DuckDB直接查询
这种架构既利用了DuckDB的分析性能,又保持了MySQL的事务特性。在我的实际项目中,这种组合将季度报表生成时间从原来的4小时缩短到15分钟。
