1. 项目概述与背景
今天想和大家分享一个最近在数据工程领域做的性能对比实验:DuckDB与MySQL在超大规模数据集下的查询速度对比。作为一名长期与数据库打交道的开发者,我经常需要处理TB级别的数据分析任务,而选择合适的数据库引擎对工作效率有着决定性影响。
DuckDB作为一个新兴的分析型数据库系统,近年来在OLAP场景下表现抢眼。而MySQL作为关系型数据库的"老将",在OLTP领域占据统治地位。但当我们面对数亿行数据的复杂分析查询时,它们各自的表现如何?这正是本次测试想要解答的核心问题。
测试环境采用了AWS的r5.2xlarge实例(8vCPU,64GB内存),数据集规模为10亿行交易记录(约120GB)。之所以选择这个量级,是因为它正好处于"普通服务器还能处理,但传统数据库已经开始吃力"的临界点,最能体现不同引擎的特性差异。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 测试环境搭建
2.1 硬件与基础软件配置
测试使用的EC2实例配置如下:
- 实例类型:r5.2xlarge
- vCPU:8核
- 内存:64GB
- 存储:500GB GP3 EBS卷(基准吞吐量125MB/s)
- 操作系统:Ubuntu 22.04 LTS
软件版本:
- DuckDB: v0.9.2
- MySQL: 8.0.33 (InnoDB引擎)
- 数据集生成工具:自定义Python脚本
提示:在云环境测试时,务必确保每次测试前重启实例并清空缓存,避免之前的测试结果影响当前数据。
2.2 测试数据集生成
我们使用Python脚本生成了一个模拟电商交易的数据集,包含以下字段:
python复制{
"order_id": "UUID",
"user_id": "Integer (1-10M)",
"product_id": "Integer (1-1M)",
"quantity": "Integer (1-10)",
"price": "Decimal(10,2)",
"order_time": "Timestamp",
"payment_method": "Enum(5种支付方式)",
"region": "Enum(8个地区)"
}
生成10亿行数据约耗时6小时,最终数据以CSV格式存储,原始文件大小约120GB。在导入数据库前,我们使用split命令将大文件分割为多个1GB的小文件,便于并行加载。
3. 数据库导入性能对比
3.1 MySQL数据导入
MySQL导入采用LOAD DATA INFILE命令:
sql复制-- 先创建表结构
CREATE TABLE transactions (
order_id VARCHAR(36),
user_id INT,
product_id INT,
quantity INT,
price DECIMAL(10,2),
order_time DATETIME,
payment_method ENUM('credit','debit','paypal','wallet','bank_transfer'),
region ENUM('north','south','east','west','central','northeast','northwest','southeast')
);
-- 导入数据(示例,实际需要对每个分割文件执行)
LOAD DATA INFILE '/path/to/data_part1.csv'
INTO TABLE transactions
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n';
导入过程中发现几个关键点:
- 直接导入120GB文件会导致内存溢出,必须分割
- 关闭索引和外键检查可显著提升速度:
sql复制SET FOREIGN_KEY_CHECKS = 0; SET UNIQUE_CHECKS = 0; SET SESSION sql_mode = 'NO_AUTO_VALUE_ON_ZERO'; - 最终导入耗时:约4小时20分钟
3.2 DuckDB数据导入
DuckDB使用COPY命令导入:
sql复制-- 创建表(DuckDB支持自动推断类型)
CREATE TABLE transactions AS
SELECT * FROM read_csv('/path/to/data_*.csv',
header=true,
delim=',',
auto_detect=true,
sample_size=-1
);
DuckDB的导入特点:
- 原生支持通配符批量导入
- 自动并行处理多个文件
- 类型推断功能强大
- 最终导入耗时:约1小时15分钟
实测发现,DuckDB的导入速度是MySQL的3.5倍左右,这主要得益于其列式存储结构和并行加载能力。
4. 查询性能测试
4.1 测试查询设计
我们设计了5类典型分析查询:
-
简单聚合:各地区销售额统计
sql复制SELECT region, SUM(quantity*price) as total_sales FROM transactions GROUP BY region; -
时间范围查询:最近一个月的高价订单
sql复制SELECT * FROM transactions WHERE order_time >= DATE_SUB(NOW(), INTERVAL 1 MONTH) AND price > 1000 ORDER BY order_time DESC LIMIT 100; -
复杂Join:用户购买行为分析
sql复制SELECT t.user_id, COUNT(DISTINCT t.product_id) as unique_products FROM transactions t JOIN (SELECT user_id FROM transactions GROUP BY user_id HAVING SUM(price*quantity) > 10000) vip ON t.user_id = vip.user_id GROUP BY t.user_id; -
窗口函数:用户消费排名
sql复制SELECT user_id, total_spend, RANK() OVER (ORDER BY total_spend DESC) as rank FROM ( SELECT user_id, SUM(price*quantity) as total_spend FROM transactions GROUP BY user_id ) t; -
多维度分析:支付方式随时间变化趋势
sql复制SELECT DATE_TRUNC('month', order_time) as month, payment_method, SUM(price*quantity) as amount, SUM(SUM(price*quantity)) OVER (PARTITION BY DATE_TRUNC('month', order_time)) as month_total FROM transactions GROUP BY 1, 2 ORDER BY 1, 2;
4.2 查询性能结果
测试采用冷启动方式(每次查询前重启服务清空缓存),各查询执行3次取平均值:
| 查询类型 | MySQL(秒) | DuckDB(秒) | 速度比 |
|---|---|---|---|
| 简单聚合 | 28.7 | 3.2 | 9.0x |
| 时间范围查询 | 14.3 | 1.8 | 7.9x |
| 复杂Join | 132.5 | 18.6 | 7.1x |
| 窗口函数 | 89.4 | 12.1 | 7.4x |
| 多维度分析 | 156.8 | 21.9 | 7.2x |
从结果可以看出,DuckDB在所有分析型查询中都显著领先,平均有7-9倍的性能优势。特别是在涉及大量数据扫描和计算的聚合操作中,DuckDB的列式存储和向量化执行引擎展现出巨大优势。
5. 深度分析与优化实践
5.1 MySQL性能优化尝试
为了公平对比,我们对MySQL进行了以下优化:
-
索引优化:
sql复制ALTER TABLE transactions ADD INDEX idx_time (order_time); ALTER TABLE transactions ADD INDEX idx_user (user_id); -
调整缓冲池:
ini复制# my.cnf 配置 innodb_buffer_pool_size = 48G innodb_buffer_pool_instances = 8 -
并行查询:
sql复制SET SESSION innodb_parallel_read_threads = 8;
优化后,简单查询有2-3倍提升,但复杂分析查询仍远慢于DuckDB。这是因为MySQL的行式存储本质不适合全表扫描型分析负载。
5.2 DuckDB的独特优势
DuckDB表现出色的核心原因:
- 列式存储:只读取查询所需的列,大幅减少I/O
- 向量化执行:利用SIMD指令并行处理数据
- 智能缓存:自动缓存常用中间结果
- 零管理:无需复杂的配置调优
一个特别实用的功能是DuckDB可以直接查询Parquet文件:
sql复制-- 无需导入,直接分析Parquet文件
SELECT region, SUM(price*quantity)
FROM 'transactions_*.parquet'
GROUP BY region;
这种"无ETL"分析模式对于临时性大数据分析非常高效。
6. 实际应用建议
根据测试结果,我的使用建议是:
-
混合使用场景:
- OLTP业务:继续使用MySQL
- 分析报表:将数据定期导出到DuckDB
-
数据流水线示例:
bash复制# 从MySQL导出到Parquet duckdb :memory: \ "EXPORT DATABASE 'output_dir' (FORMAT PARQUET) \ FROM mysql_scan('host=localhost dbname=ecommerce', 'transactions')" # 直接分析Parquet duckdb ecommerce.duckdb \ "SELECT * FROM 'output_dir/*.parquet' WHERE ..." -
何时选择DuckDB:
- 需要交互式分析大数据
- 临时性复杂查询
- 嵌入式分析应用
- 数据科学工作流
-
何时坚持MySQL:
- 高并发事务处理
- 需要严格ACID保证
- 已有成熟运维体系
7. 遇到的坑与解决方案
7.1 内存不足问题
在初期测试中,DuckDB在处理超大JOIN时曾出现OOM错误。解决方案:
sql复制-- 设置内存上限
SET memory_limit='32GB';
-- 使用磁盘溢出
SET temp_directory='/mnt/tmp';
7.2 数据类型兼容性
发现某些MySQL的TIMESTAMP精度在导入DuckDB后丢失。解决方法:
sql复制-- 明确指定时间精度
CREATE TABLE transactions AS
SELECT
order_id,
user_id,
product_id,
quantity,
price,
strptime(order_time, '%Y-%m-%d %H:%M:%S.%f') as order_time,
payment_method,
region
FROM read_csv('input.csv');
7.3 并行导入优化
当CSV文件数量超过1000个时,直接使用通配符会导致性能下降。最佳实践是:
bash复制# 先将小文件合并为中文件(每个约10GB)
cat data_*.csv | split -l 100000000 -d -a 3 - merged_
8. 性能对比总结
经过全面测试,可以得出以下结论:
- 导入速度:DuckDB显著快于MySQL(3-4倍)
- 存储效率:DuckDB的压缩存储节省约40%空间
- 查询性能:
- 简单查询:DuckDB快5-10倍
- 复杂分析:DuckDB快7-9倍
- 资源消耗:DuckDB内存管理更高效
- 易用性:DuckDB无需复杂调优
最后分享一个实用技巧:对于超大规模数据集,可以先用DuckDB快速探索和原型开发,然后将优化后的查询模式移植到生产环境的数据仓库(如Snowflake或Redshift)中。这种"本地开发+云端部署"的工作流在我的团队中效果非常好。
