十万行订单导出优化实战:从超时报错到秒级下载的四个改造步骤

上个月接到平台运营同事的求助:每月给供应商对账的订单导出功能,跑了一半直接报 502。数据量从去年的两万行涨到现在十万行出头,这套去年还好好的导出功能,今年彻底跑不动了。这个问题在中小系统里非常典型——功能没变、代码没改,数据量一涨就垮。这篇文章把整个排查和改造过程记录下来,四个步骤,每一步都有数据验证。

先说症状:导出按钮点了之后页面转圈约 90 秒,然后报错。服务器日志显示 PHP 进程内存超限被杀。运营侧的诉求很直接:十多万行订单,最好一分钟内拿到文件,导出期间平台其他功能不能受影响。

核心思路:报表导出类功能的性能问题,九成出在"一口气"三个字上——一口气查全表、一口气攒进内存、一口气写文件。改造方向不是换更贵的服务器,而是把"一口气"拆成"小口慢咽":分批取、边取边写、异步跑、落盘后给链接。

一、先定位:三个嫌疑,两个实锤

动手前先用日志和慢查询记录把问题定位清楚,避免改错方向。排查结论如下:

嫌疑点验证方法结论
SQL 查询太慢慢查询日志 + EXPLAIN实锤:单条 SQL 全表取 10 万行,扫描 40 秒
内存被撑爆分段打印 memory_get_usage实锤:结果集整体载入占 800MB,超限被杀
Excel 库太慢用 1 万行样本单独压测排除:生成本身只占 6 秒,不是瓶颈

定位清楚之后改造顺序就定了:先解决查询和内存,再解决用户体验。很多团队接到导出超时的第一反应是"加内存"或"换异步队列框架",其实先花半小时做定位,改造量能小一半。

二、四个改造步骤

第一步:查询分批,别一口气拉全表

把单条 SELECT * FROM orders 改成按主键游标分批查询:每批 5000 行,用上一批最大的 id 作为下一批的起点。这里有个容易踩的坑——用 LIMIT OFFSET 翻页,页码越深越慢,翻到后面每页还是要扫过前面所有行;按主键游标走索引,每批耗时恒定在 0.3 秒左右。十万个订单 20 批跑完,查询总耗时从 40 秒降到 7 秒。另外别忘了给查询里用到的状态和时间字段补上合适的联合索引,分批只能解决"扫的次数",索引才能解决"每次扫多少"。

第二步:边查边写,内存占用从 800MB 降到 40MB

原来把全部结果攒进数组再交给 Excel 库生成,改成每查一批就立刻写进文件、写完释放变量。文件格式选了 CSV 而不是 XLSX——CSV 是纯文本追加写,天然适合流式;运营侧用表格软件照样打开。如果业务必须 XLSX,可以用支持流式写出的方式按行刷盘。改完后进程峰值内存稳定在 40MB 上下,再也不碰内存上限。

第三步:同步改异步,导出和页面操作解耦

即使总耗时降到 30 秒以内,让用户在页面上等 30 秒也不合适。改成点击导出后立即返回"任务已创建",后台进程跑批,前端每 3 秒轮询一次进度。这一步的关键是给用户看得见的进度:已处理 45000 / 102000 行,比一个干转圈的 loading 体验好得多,也大幅减少重复点击。实现上没有引入任何新框架:任务记录就写一张数据库表,后台用常驻的队列进程消费,中小系统的体量完全够用,没必要为了一个导出功能上消息队列中间件。

第四步:文件落盘加过期清理,别把服务器当网盘

生成的文件放到下载目录,按"日期+用户"命名,保留 7 天自动清理。顺手加了两个防护:同一用户 5 分钟内不重复创建相同条件的导出任务;文件下载链接带随机 token,避免被遍历爬走。这两行代码不多,但都是上线后很容易出事故的地方。

改造前后对比:总耗时 90 秒超时报错 → 28 秒稳定出文件;内存峰值 800MB+ → 40MB;用户等待体验 = 干等转圈 → 实时进度条;失败后只能重试 → 失败任务可查、断点可续。

三、上线验证与遗留问题

上线后跑了三周,日均导出二十多次,零失败。压测时特意把数据造到 50 万行,总耗时 2 分 10 秒,仍能正常出文件——按当前订单增速,这套方案至少还能撑两年。遗留一个待办:分批查询对"导出瞬间数据一致性"有轻微影响,前后批次之间可能有极少量新订单穿插,对账场景影响可忽略,但如果是财务级精确报表,需要加只读快照或按时间戳圈定范围。

可复用清单:① 分批查询用主键游标,不用 LIMIT OFFSET;② 能 CSV 就别 XLSX,流式追加写;③ 超过 10 秒的任务一律异步加进度反馈;④ 导出文件落盘加 token 和过期清理;⑤ 上线前用 3 倍于当前数据量的样本压测。

四、写在最后

这类"数据涨了功能垮了"的问题,本质上不是事故,而是系统成长的正常代谢。排查的顺序永远是先定位再动手:慢查询日志、内存打点、样本压测,三个手段花不了半小时,能把改造范围缩小一半以上。如果你家的系统也有导出慢、查询慢、页面卡的问题,可以拨 18833330416,或在网站留言。我们会先帮你定位瓶颈出在哪一层,再决定要不要改、怎么改——很多问题改十行代码就够了,不需要推倒重来。