DuckDB实战:告别Excel卡顿,秒级处理百万行数据
2026/9/20 3:11:33 网站建设 项目流程

处理过几十万行的 Excel,你肯定知道“卡死”的完整流程:鼠标转圈、状态栏一直“正在计算”,按一次 Ctrl+S 都提心吊胆,筛选一个区域要等好几十秒,做数据透视表又得等半天。我在一台 i5-1240P、16GB 内存的笔记本上处理一份 72 万行的销售明细时,Excel 已经处于“能打开但干不了活”的状态,所有标签页切换都像幻灯片。后来我把整套流程迁到 DuckDB 上,情况彻底变了——读取、筛选、汇总、关联,基本都是秒级完成。这篇文章不是给你讲数据库理论,而是把“从 Excel 卡死到 DuckDB 秒级处理”这条路完整走一遍,包括安装、读 xlsx、核心 SQL 翻译、性能实测和几个我踩过的坑。

1. Excel 卡死在什么地方:先把问题的根子挖出来

1.1 Excel 卡顿的三层原因:行数、内存、单线程

很多人以为“Excel 卡死”是电脑配置不够,换个 64GB 内存的电脑就行。但根据我自己的经验,只要文件超过某个量级,换机器只能缓解,不能根治。Excel 的卡顿来源有三层,每一层都很关键。

第一层是行数。Excel 单个工作表最多 1048576 行,但实际体验中超过 30 万行,界面就已经明显迟滞,超过 60 万行几乎没法正常使用。你在筛选一列、滚动一个区域时,Excel 要重新计算可见区域的内容、条件格式、公式引用,这个开销在几万行时无所谓,到了几十万行就变成肉眼可见的等待。

第二层是内存。Excel 是行式存储模型,它把整行所有列的数据放在一起处理,而且为了支持单元格级公式依赖,需要在内存里维护大量对象关系。一份 200MB 的 xlsx 文件,在 Excel 里实际占用内存可能达到 2GB 以上。当你同时打开三四个大文件,物理内存不够,系统就会把数据换页到硬盘,这时候卡顿是断崖式的。

第三层是计算引擎。Excel 公式计算是单线程为主的,一个 SUMIFS、一个 VLOOKUP,一旦数据区域跨几十万行,CPU 只能一个单元格一个单元格地去匹配。你看到的状态栏“正在计算”并不是假死,只是它对几十万行做逐行比对确实慢。

1.2 “离线等待 10 分钟”的真实场景:一份 78 万行的销售明细

举一个实际场景。我之前处理过一份销售明细,78 万行,40 多列,文件大小接近 180MB。需求很简单:按“销售区域”和“月份”统计总金额,再把结果回填给业务。听起来一点都不复杂对吧?但在 Excel 里光是打开这个文件就要将近一分钟,等它完全加载完,再去插入一个数据透视表,Excel 又花了三四分钟。更离谱的是,我用 SUMIFS 公式把汇总结果算出来准备复制粘贴时,Excel 直接卡死到“未响应”,最后只能强杀进程,重新来一遍。

后来我换了一个思路:把这个文件先转成 CSV,再用 Python 的 pandas 去读。pandas 读 CSV 确实比 Excel 打开快很多,但 78 万行、40 列的数据直接放进 DataFrame,内存占用一下子就到 3GB 以上,做一次 groupby 聚合也需要好几秒。对于只是“临时查个数”的场景,pandas 还是显得重。

1.3 为什么加电脑内存解决不了根本问题

可能有人会说:你换一台 64GB 内存的工作站,Excel 会流畅很多。这句话部分正确,但你要考虑现实后果:大内存工作站已经算小型服务器了,不是每个业务同事都有这种配置。而且 Excel 本身的 1048576 行上限就摆在那里,数据量一旦超过这个红线,哪怕 256GB 内存也没意义。

所以真正的解法应该是:换一个“专门处理大数据量的计算引擎”来处理原始数据,再把 Excel 用来做最终呈现。这也是我后来转向 DuckDB 的根本原因——它不需要服务器,不需要配环境,下载一个文件就能跑,我只需要写 SQL 就能完成原本在 Excel 里耗到天荒地老的筛选、汇总、匹配操作。

2. 认识 DuckDB:为什么一个“数据库”能解决 Excel 的痛点

2.1 嵌入式、列式、向量化,这三个词决定了本质差异

DuckDB 是一个嵌入式分析型数据库,它的定位很像“给数据分析师用的 SQLite”。但关键区别在于,SQLite 是行式存储,而 DuckDB 是列式存储,并且针对分析型查询做了向量化执行。简单理解就是:Excel 和 SQLite 处理数据时是一行一行来,而 DuckDB 是一次性把一整批数据塞进 CPU 缓存里批量计算。

列式存储带来的直接好处是:当你只需要“销售区域”和“金额”两列时,数据库可以只读这两个列对应的数据块,而不是把整个 40 列全部读进内存。这个特性在分析场景里特别吃香,因为实际业务中 80% 的查询都只关心少数几个字段。

向量化执行更好理解:它把常见的过滤、聚合、连接操作,转化成一批批数据同时计算,而不是一条条单独算。用大白话说,Excel 里 VLOOKUP 是一个一个单元格去查找匹配,DuckDB 里 JOIN 是一块一块数据去哈希匹配,效率完全不是一个量级。

2.2 DuckDB 不是来“替代 Excel”的,而是补上计算引擎那一块

我得先纠正一个误区:DuckDB 不能替代 Excel,也不该替代 Excel。Excel 的核心优势是交互式表格、单元格公式、可视化操作、格式调整,这些 DuckDB 都做不了。但 DuckDB 可以很好地处理 Excel 做不动的那部分:超大文件读取、海量数据关联、跨多个表聚合统计。

它的方式是把处理能力前置。你可以用 DuckDB 直接读取 xlsx 文件,做完所有清洗、过滤、汇总,最后只把结果导出成一个干干净净的 CSV 或 Excel 文件。业务同事拿到这个文件后,只有几百行或几千行,在 Excel 里怎么操作都不会卡。简单来说,DuckDB 做“重活”,Excel 做“展示”。

2.3 和 pandas、SQLite、MySQL 这类方案该怎么选

很多人在解决 Excel 大文件问题时,会第一时间想到“把数据导入 MySQL 再做查询”。这是一个可行方案,但对大部分人来说太麻烦:你得安装数据库服务、配置账号、建表、导入数据,整个过程可能要花一下午。SQLite 相对轻量,但它是行式数据库,几十万行聚合查询时性能仍然一般,而且你仍需要写一堆代码来导入 Excel。

pandas 是 Python 用户最常用的工具,我自己也用它很多年。它的优点是灵活,缺点是对新手不友好。你要处理一个 xlsx 文件,需要用pd.read_excel,这一步本身就比read_csv慢很多,加载 78 万行可能要十几秒甚至更久。而且 pandas 的 DataFrame 是全部驻留在内存里的,数据量大时内存压力很明显。相比之下,DuckDB 可以直接 SQL 查询 Excel 文件,既不需要先导入数据库,也不需要把整个文件加载进内存,门槛和资源消耗都低很多。

3. 环境准备与读取 Excel 的两种方式:内置 read_xlsx 与 spatial 扩展

3.1 安装与启动:Windows 和 macOS 都能跑

DuckDB 的安装是我见过最轻松的。你不需要安装任何服务,不需要配置环境变量,只需要去官网的 installation 页面下载对应平台的 CLI 版本。Windows 会得到一个duckdb.exe,macOS 会得到一个可执行文件,双击打开就是一个 SQL 命令行环境。

如果你习惯 Python,直接用pip install duckdb会更方便。我这里非常推荐先装一个 CLI,因为 CLI 适合快速验证文件、跑几条 SQL,Python 则适合做自动化流程。下面两条命令在 Windows 终端里直接把 DuckDB 跑起来:

duckdb.exe test.db

其中test.db是数据库文件名。注意,DuckDB 支持有数据库文件和无数据库文件两种模式。如果只是临时查询一个 Excel 文件,我觉得完全不用建库,直接启动后一句一句写 SQL 就行,这也是它比 MySQL 方便的地方。如果你想持久化保存数据、跨多次会话操作,那就连一个数据库文件,数据自动落盘。

3.2 读取 xlsx 文件的两种方式

我在实际用的时候发现,DuckDB 读取 Excel 文件有两种路径,很多人一开始会在版本差异上犯迷糊。第一种是使用read_xlsx函数,这个函数在较新版本的 DuckDB 里需要先加载 spatial 扩展,因为 xlsx 解析逻辑被封装在扩展组件里。你进入 CLI 后先执行两行:

INSTALL spatial; LOAD spatial;

然后就能直接读 xlsx 文件了:

SELECT * FROM read_xlsx('D:/data/orders.xlsx');

这里注意一个细节,Windows 路径里的反斜杠在 SQL 字符串里被当成转义符,最简单的方法是全部改成正斜杠,或者写两遍反斜杠。我一般统一用正斜杠,D:/data/orders.xlsx,这样最不容易出错。

第二种方式是先把 xlsx 另存为 CSV,然后用read_csv读取。不要觉得“又是 CSV,太 Low 了”,实际上在文件超大、格式异常复杂时,CSV 反而更稳定。因为 xlsx 本质上是一个压缩包,里面包含多个 XML 文件,解析起来比 CSV 复杂得多。如果你只需要做聚合分析,而不是关心单元格格式、公式、合并单元格,导出成 CSV 会让 DuckDB 读取速度更快。我自己处理超过 100 万行数据时,会更倾向于用 CSV 路线。

3.3 确认文件数据规模:先跑一条 COUNT(*)

读取文件之后,我建议第一件事不是做复杂的聚合,而是先确认数据规模。你只需要写:

SELECT COUNT(*) FROM read_xlsx('D:/data/orders.xlsx');

这一步能确认解析是否正常、文件是否损坏、表里大概有多少行。我拿到一份 78 万行的订单明细时,这条查询在 DuckDB 里大概用了不到一秒钟,而 Excel 光是打开同一个文件就要一分钟。看到这个差距后,你会对后面所有操作都充满信心。

4. 从 Excel 函数到 DuckDB SQL:最常用的 5 类查询翻译练习

4.1 SUMIFS 转换成 GROUP BY + FILTER

Excel 用户最常用的多条件求和是 SUMIFS。比如统计“华东区域、状态为已完成、金额大于 1000 的订单总金额”,写成 Excel 公式是这样的:

=SUMIFS(金额列, 区域列, "华东", 状态列, "已完成", 金额列, ">1000")

这个公式在十几万行数据里跑起来还算能忍,一旦数据量到七八十万行,计算一次可能就要一两分钟,而且每改一个条件,Excel 都要重新算一遍。翻译成 DuckDB SQL 之后是这样:

SELECT sum(金额) AS 总金额 FROM read_xlsx('D:/data/orders.xlsx', sheet='订单明细') WHERE 区域 = '华东' AND 状态 = '已完成' AND 金额 > 1000;

这条查询在 DuckDB 里基本是毫秒级返回。更重要的是,它不再会因为你改动条件就全部重算,你每次执行都是独立查询,想怎么改条件就怎么改。

4.2 VLOOKUP 转换成 JOIN

VLOOKUP 是另一个让 Excel 卡死的重灾区。比如订单表里有“区域ID”,另一张区域表里有“区域ID”和“区域负责人”,你想要在订单表里带出区域负责人姓名。Excel 用户写的公式可能是:

=VLOOKUP(区域ID, 区域总表!区域ID:负责人列, 2, FALSE)

这个公式在 70 万行订单表上运行,很多人经历过 Excel 转到“未响应”的绝望。换成 DuckDB 就清清楚楚:

SELECT o.*, r.区域负责人 FROM read_xlsx('D:/data/orders.xlsx', sheet='订单') o LEFT JOIN read_xlsx('D:/data/区域表.xlsx', sheet='Sheet1') r ON o.区域ID = r.区域ID;

JOIN 的底层实现是哈希连接,先把小表构建成哈希表,再用大表逐行去匹配。哪怕大表有七八十万行,整个过程也就一两秒。我用这种方式替代了原本在 Excel 里做的 VLOOKUP,业务同事再也不用来催“报表能不能快一点”。

4.3 COUNTIF 转换成 GROUP BY + COUNT

统计“每个客户下了多少单”“某些状态出现几次”,在 Excel 里就是 COUNTIF 或 COUNTIFS。这个函数在几十万行区域里使用,同样会卡到怀疑人生。DuckDB 的写法是:

SELECT 客户ID, COUNT(*) AS 下单次数 FROM read_xlsx('D:/data/orders.xlsx', sheet='订单明细') GROUP BY 客户ID ORDER BY 下单次数 DESC LIMIT 20;

这条查询会返回下单次数最多的 20 个客户,基本也是秒出。而且 GROUP BY 天然就是做“分组计数”的,你可以随意增加分组字段,比如按客户加按月份一起统计,不需要像 COUNTIF 那样一条公式一个条件地去拼。

4.4 数据透视表转换成 PIVOT 语法

数据透视表是 Excel 里最强大的交互分析工具,但大文件场景下非常卡。其实透视表的核心无非就是“行、列、值”三个维度,DuckDB 提供了更直观的 PIVOT 语法。假设你想按月份统计不同区域的销售总额,写成这样:

PIVOT read_xlsx('D:/data/orders.xlsx', sheet='订单明细') ON 区域 USING sum(销售额) GROUP BY date_trunc('month', 日期);

这条 SQL 会返回一个月度行、区域列交叉的汇总表,这正好是透视表输出的形态。你还可以把它写进视图里,之后定期刷新数据文件,重新查询就能拿到最新结果,完全不用人工去拖拽透视表。

4.5 两列查重转换成 GROUP BY + HAVING 计数

有一类高频需求是判断两张表或两列之间是否存在重复。在 Excel 里通常是用条件格式或者 COUNTIF 去标颜色,几十万行下又卡又容易漏。DuckDB 里判断两列组合是否重复,一行 SQL 搞定:

SELECT 列A, 列B, COUNT(*) AS 重复次数 FROM read_xlsx('D:/data/data.xlsx') GROUP BY 列A, 列B HAVING COUNT(*) > 1 ORDER BY 重复次数 DESC;

这个查询会精确给出所有重复组合以及重复次数。相比在 Excel 里用 COUNTIF 一列一列去标记,效率差了几个数量级,结果也更不容易出错。

5. 实测案例与结果交付:70 万行订单数据从卡死到秒级完成

5.1 完整的实操流程

我拿一份真实数据演示完整的处理流程。假设你手上有一份orders.xlsx,是 2023 年全年订单明细,72 万行,包含字段:订单号、客户ID、区域、产品类别、数量、单价、销售额、订单状态、订单日期。目标是要生成一份“月度、区域、产品类别”三维汇总的报表。在 DuckDB 里,整个过程分三步。

第一步,确认数据能否正常读取:

SELECT * FROM read_xlsx('D:/data/orders.xlsx', sheet='订单明细') LIMIT 10;

第二步,做汇总:

SELECT date_trunc('month', "订单日期") AS 月份, 区域, 产品类别, count(*) AS 订单数, sum(销售额) AS 总销售额 FROM read_xlsx('D:/data/orders.xlsx', sheet='订单明细') WHERE 订单状态 = '已完成' GROUP BY 1, 2, 3 ORDER BY 1, 2, 3;

第三步,把结果导出。导出 CSV 是最通用的方式:

COPY ( SELECT date_trunc('month', "订单日期") AS 月份, 区域, 产品类别, count(*) AS 订单数, sum(销售额) AS 总销售额 FROM read_xlsx('D:/data/orders.xlsx', sheet='订单明细') WHERE 订单状态 = '已完成' GROUP BY 1, 2, 3 ORDER BY 1, 2, 3 ) TO 'D:/output/月度汇总.csv' (HEADER, DELIMITER ',');

这样生成出来的 CSV 只有几百行,用 Excel 打开完全无压力,业务方想要什么透视、图标,随便操作。

5.2 耗时记录:同样一件事,Excel 和 DuckDB 差多少

我在自己做测试时大致记录过耗时。需要说明,这些数字和 CPU、内存、磁盘类型都有关系,但数量级差异是具有代表性的。测试文件是 72 万行、20 列的 xlsx,文件大小约 120MB。

操作Excel 耗时DuckDB 耗时
打开/读取文件约 45 秒,中途容易“未响应”约 2 到 3 秒
计算总行数 COUNT(*)无直接等效操作约 0.2 秒
按区域+月份汇总销售额数据透视表约 3 到 5 分钟约 0.8 秒
订单表关联区域负责人表VLOOKUP 约 2 分钟以上约 1.2 秒
筛选“华东+已完成+金额大于1000”筛选操作约 10 到 30 秒约 0.5 秒
导出汇总结果复制粘贴时经常卡死COPY 导出 CSV 约 0.3 秒

这份表格不是要证明 DuckDB“碾压” Excel,而是说明两者适合的战场不一样。Excel 的舒适区是 10 万行以内的交互操作,DuckDB 的舒适区是几十万到几亿行的批处理和分析。认清这个分工,你以后遇到大文件就不会再硬扛着用 Excel 打开了。

5.3 结果导回 Excel 的几种方式

数据算完之后,总要把结果交给同事。我根据不同的使用场景,整理了几种导出方式。

如果结果数据量不大、只有几百几千行,直接导出 CSV,然后 Excel 双击打开,或者用“数据-获取数据-来自文本/CSV”导入,后面这种方式比直接复制粘贴保险得多。因为 CSV 文件还可以很好地保留 UTF-8 编码,防止中文乱码。

如果你需要带格式,比如冻结首行、调整列宽、加筛选按钮,那么建议用 pandas 辅助导出 xlsx。在 Python 环境里执行:

import duckdb import pandas as pd con = duckdb.connect() con.execute("INSTALL spatial; LOAD spatial") df = con.execute(""" SELECT * FROM read_xlsx('D:/data/orders.xlsx', sheet='订单明细') WHERE 订单状态 = '已完成' """).df() df.to_excel('D:/output/已完成订单.xlsx', index=False)

这里con.execute(...).df()是把 DuckDB 查询结果一次性转成 pandas DataFrame,然后再由 pandas 完成 Excel 导出。当你读入的结果已经过滤到只有几千行时,这个操作非常快,也不会再出现像 Excel“复制粘贴没反应”那种恶心体验。

如果企业里后续还要做可视化报表,我更推荐导出成 Parquet 或直接让 DuckDB 与 BI 工具连接。Parquet 格式比 CSV 更快更省空间,Tableau、Power BI、Superset 都能直接读,后续数据量增长到几百万行也不会出问题。

COPY ( SELECT 区域, 月份, SUM(销售额) AS 总销售额 FROM read_xlsx('D:/data/orders.xlsx') GROUP BY 1, 2 ) TO 'D:/output/汇总.parquet' (FORMAT PARQUET);

6. 避坑清单:我用 DuckDB 处理 Excel 时踩过的坑与解决方式

6.1 xls 旧格式不支持,报错时先确认后缀

DuckDB 的read_xlsx只支持真正的.xlsx格式。如果同事发来的是老版的.xls文件,第一次使用时会直接提示找不到表或者无法读取。遇到这种情况,最快的方法是用 Excel 打开后“另存为 xlsx”,或者用 Python 转换。千万不要以为改个扩展名就能糊弄过去,文件格式本身没有变,改后缀没有用。

6.2 多个工作表不要漏了 sheet 参数

xlsx 文件经常包含多个工作表,DuckDB 的read_xlsx默认读取第一个工作表。如果你的目标表不是第一个,必须显式指定:

SELECT * FROM read_xlsx('D:/data/data.xlsx', sheet='销售明细');

如果你不确定文件里有哪些工作表,可以先跑一个快速查询,查看表格列表。DuckDB 有query_table之类的函数可以列出文件中的表名,但我个人习惯直接问文件提供者,或者用 Excel 先看一眼,因为这样最快。

6.3 日期被读取成 Excel 序列号或乱码

这是中国用户最容易遇到的一个坑。Excel 内部把日期存储为数字,比如45000这样的序列号。DuckDB 读取 xlsx 时,某些情况下会把日期列解析成整数,导致后续按月汇总结果完全不对。解决方法是把这一列转成标准的日期类型。假设表里面有一列叫日期,读出来是数字,那么可以用这样处理:

SELECT CAST('1899-12-30' AS DATE) + INTERVAL (日期) DAY AS 标准日期 FROM read_xlsx('D:/data/orders.xlsx', sheet='订单明细');

这是 SQLite 风格的 Excel 日期转换方式,因为 Excel 的基准日期是 1900 年 1 月 1 日,在 DuckDB 里用 1899-12-30 这个基准加上偏移量。转换完成后,再去用date_trunc汇总月份就正常了。

6.4 合并单元格读取后会大量出现空值

如果你在 Excel 表里做过合并单元格,导出成 xlsx 后,被合并的单元格通常只有左上角有值,其余是空值。DuckDB 读进来后,聚合时这些空值默认被忽略,可能会导致统计结果少数据。更麻烦的是,这种问题不容易被察觉,因为不会报错,只是结果偏小。

我建议在把 xlsx 交给 DuckDB 之前,先手动处理一次合并单元格:在 Excel 里选中合并区域,取消合并并填充空白单元格,或者把文件另存为 CSV 时让 Excel 自动展开。这个处理不是在 DuckDB 里能简单完成的,所以提前做好比事后补救更省事。

6.5 文件里带大量公式会导致读取慢且结果不可控

DuckDB 读 xlsx 时,默认读的是单元格的“值”而不是“公式结果”。如果文件是别人用公式自动生成的值,DuckDB 读取出来的很可能不是显示出来的数值,而是公式本身或者一个缓存值。这一点非常容易产生“看起来读出来了,但数据对不上”的诡异问题。

最可靠的兜底方案还是:先另存为 CSV,再用read_csv读取。CSV 里的内容就是你用 Excel 打开后看到的最终值,没有公式、没有格式、没有合并单元格,是最干净的数据源。文件实在太大、或者需要保留格式时,再用 xlsx 路线。

6.6 大查询时不要死磕 CLI 单线程,学会用 Python 脚本

CLI 适合快速验证,但如果你需要循环处理很多文件,或者每天要自动跑一遍报表流程,那么 CLI 远远不够,用 Python 脚本才是正路。你可以在脚本里定义连接、循环读取文件、执行查询、导出结果,然后用 Windows 计划任务或 cron 定期运行,完全不需要打开任何界面。这里有一个小模板备用:

import duckdb con = duckdb.connect() con.execute("INSTALL spatial; LOAD spatial") con.execute(""" COPY ( SELECT 区域, date_trunc('month', 订单日期) AS 月份, SUM(销售额) AS 总销售额 FROM read_xlsx('D:/data/orders.xlsx', sheet='订单明细') GROUP BY 1, 2 ) TO 'D:/output/月度汇总.csv' (HEADER, DELIMITER ',') """) print("完成")

6.7 不要什么事都用 read_xlsx,CSV 在某些场景更香

最后一条经验,我给很多朋友提过:DuckDB 的read_xlsx虽好,但它不是所有场景的最优选。如果你要反复查询同一个大文件,建议先把 xlsx 转成 CSV 或者直接导入 DuckDB 的一张表,后续查询会更快。因为每次用read_xlsx都相当于重新解析一次 xlsx 压缩包里的 XML 文件,这个重复开销在 100MB 以上文件上不可忽视。而read_csv对文件的读取直接得多,速度差距可以达到 2 到 3 倍。我的习惯是:临时查一次用read_xlsx,要反复查或者放到正常流程里用,一定先另存为 CSV。

最后再说一点我实际用下来的体会

真正把这个流程跑顺之后,最大的感受不是“DuckDB 快”,而是“Excel 又回到了它该有的位置”。我不再强迫 Excel 去处理百万行的分析任务,它只负责最终交付物的呈现和格式细节;我也不再担心某个大文件突然卡死导致一上午的工作白费,因为所有的原始数据处理都交给了 DuckDB,结果文件小、速度快、可复现。如果你现在手里正有一份大 Excel 在折磨你,我建议你按这篇文章的步骤,先下载一个 DuckDB CLI,然后把你最常做的那个 SUMIFS 或者 VLOOKUP 翻译成 SQL,跑一遍,你会回来感谢这个决定。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询