如果你做过两年以上的后端或者数据分析,一定碰到过这种尴尬:业务库MySQL平时挺稳,查询一旦变成几亿行的汇总统计,接口就开始几秒甚至几十秒卡住。老板只是要一个区域销售月度数字,你却不敢直接在生产库上跑,只能半夜导数据出来算。这篇文章源于我自己被这种场景反复折磨后的一个疑问:同样是SQL查询,换一个面向分析的引擎到底能快多少?为了搞清楚,我在一台普通Linux服务器上创建了一张两亿行订单明细表,分别放在MySQL 8.0和DuckDB里,完成点查、范围过滤、JOIN、GROUP BY和全表统计五类查询。文章会给出每条SQL、参数、结果和实验坑,适合MySQL用得比较多、又想了解DuckDB性能边界的人,也适合想知道OLTP和OLAP差距在哪的开发/数分同学。
1. 为什么要在超大数据集下做这次对比
1.1 传统MySQL扛业务没问题,扛分析却很吃力
MySQL是我这么多年用得最多的数据库,OLTP场景下它确实稳定可靠。但只要数据上涨到一定量级,把分析SQL直接扔给它就跑得非常别扭。几百万行数据你感觉不到,等到表里有几千万甚至上亿行,一个简单的 SUM 就可能扫全表,一个 GROUP BY 排序就可能把临时表写满磁盘,接口延迟直接从几十毫秒恶化到几十秒。更麻烦的是,生产库上还有大量写入和事务,你跑一条大查询,可能把内存、IO和锁都吃掉了,业务侧跟着受影响。
常见的处理方案是分库分表、提前建汇总表、或者把数据同步到数仓再分析。这些方案本身没问题,但维护成本不低:分库分表让查询逻辑变复杂,汇总表需要额外开发,数仓则引入了完整的大数据组件栈。对一个中小团队来说,很多时候我们只想低成本地解决“超大数据集下偶尔跑几条分析SQL”的问题,并不想养一套Hadoop体系。
1.2 DuckDB为什么值得被拉进候选名单
DuckDB近几年讨论度很高,它是一款嵌入式列式OLAP数据库,定位有点类似SQLite,但SQLite是行存、面向事务,而DuckDB是列存、面向分析。它没有独立服务进程,不像MySQL需要安装后启动mysqld并维护连接,而是一个库文件,应用进程直接嵌入进去就能执行SQL。它对Parquet、CSV这些数据文件支持非常好,一条 SELECT 可以直接扫本地的几十GB文件。
我一开始对这类“嵌入式数据库”的性能持怀疑态度,直觉里觉得这顶多算实验室玩具。但真正了解后发现,DuckDB采用的是向量化执行引擎,处理数据时不是一行一行地跑,而是一次处理一批数据,再配合多核并行和二进制的列式存储布局。这种设计和很多真正的数仓引擎是同一条路线,只是包在一个极简的部署形态里,所以非常适合拿来在超大数据集上做低成本分析。
1.3 这次对比测什么、不测什么
我并不是想写一篇“谁取代谁”的引战文,更不是用压测工具去模拟两套数据库在高并发写入下的TPS差异。这次对比只聚焦一个非常具体的痛点:当数据量大到MySQL开始难受时,DuckDB是不是一个足够轻的补充方案。
所以数据类型选了订单明细,因为这是后端和数据分析最常见的超大数据集形态。MySQL这边扮演的是一个“被硬拉着做分析OLTP库”,DuckDB则作为分析引擎去查同样的数据。我不测高并发写入,不测复杂事务,也不测两套系统的运维能力,只测五类真实业务里最高频的查询SQL,然后结合执行计划和引擎原理去解释快慢背后的原因。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 测试环境与数据集搭建
2.1 测试机器与运行参数
这次没有用很大的物理机,反而故意用了一台普通配置的服务器,因为大多数团队的生产环境并没有那么多高配机器,我手上的MySQL和DuckDB会被放到同一条起跑线上。测试机是一台单机Linux,8核CPU、16GB内存,数据盘是NVMe SSD,操作系统用Ubuntu 22.04。
MySQL用的是8.0.36,DuckDB用的是1.1.x版本。MySQL配置里最重要的参数是 innodb_buffer_pool_size,我把它调到12GB,基本上把可用内存的大部分都给了InnoDB,避免让人吐槽“MySQL没调优”。DuckDB侧的对应参数是 memory_limit,同样设置成了12GB,线程数设置成8。两台引擎分到一样的资源,这样最后的结果才有比较价值。
cnf复制[mysqld]
innodb_buffer_pool_size = 12G
innodb_log_file_size = 1G
max_connections = 200
sql_mode = STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
DuckDB在CLI里执行下面两条PRAGMA:
sql复制SET memory_limit='12GB';
SET threads=8;
2.2 表结构设计与两亿行数据怎么造
为了贴近真实场景,我定了一张订单事实表 orders,字段不算多,但覆盖了最常见的过滤和聚合条件:订单号、客户ID、商品ID、区域ID、下单日期、订单金额、数量、状态。另外做了一张客户维表 customers,客户量是800万行,用来测多表JOIN。
我在MySQL里建表时有意保留了两个二级索引,一个是 idx_order_date,因为按时间范围过滤是订单查询最典型的场景;另一
