在 Sales → Orders 选择一年数据导出 CSV,页面转圈后 504;把 PHP timeout 调大只能让请求占用 worker 更久。订单 Grid 导出通常同步加载筛选集合、格式化列并输出文件,自定义列的一对多 join 还可能放大行数。
先证明慢在查询还是生成
用一个月、一周、一天逐步缩小范围,记录 SQL 时间、结果行数、PHP 内存和生成阶段耗时。页面 Grid 快不代表导出快:导出可能绕过分页并执行不同 renderer。
检查最终 SQL
扩展把订单商品、地址或支付附加信息 join 到 Grid 时,可能让一张订单变多行并触发 DISTINCT/GROUP BY。用慢日志和 EXPLAIN 检查扫描行、临时表与排序;不要直接给所有字段加索引。
分批要用稳定游标
按 entity_id 或 created_at+entity_id 递增读取,每批固定数量,记录断点。大 OFFSET 越往后越慢,导出期间有新订单还会造成重复/遗漏。若需要严格快照,先固定最大 entity_id 和时间边界。
WHERE entity_id > :last_id
AND entity_id <= :max_id
ORDER BY entity_id ASC
LIMIT 1000
异步导出更适合大数据
后台只创建任务,Consumer 分批生成到受保护存储,完成后通知管理员下载。文件应有短期有效期、权限校验和清理策略;订单 CSV 含客户信息,不能放在公开 pub 目录用可猜 URL 下载。
资源参数只能作为容量保护
适度提高内存/超时可辅助,但应配合批次释放集合、流式写 CSV 和限制最大范围。修复后用小、中、大三档数据验证行数、金额合计、字符编码和权限,确保性能优化没有漏单或重复订单。
CSV 本身也要防公式注入
订单中的姓名、公司、备注等用户输入若以 =、+、-、@ 开头,电子表格打开时可能当作公式执行。导出层应按安全策略转义,且保持 UTF-8/BOM 与分隔符一致。不要因为是后台管理员下载就忽略恶意订单数据。
避免 N+1 格式化
循环每行再加载 order repository、客户、商品或地址,会把一条导出变成数万次查询。让主查询批量选择所需列,对一对多数据先批量映射;敏感列只对有权限角色输出。记录 SQL 次数随行数的变化,比只看总时间更能确认优化。
任务可恢复性
异步导出中断后应从最后稳定游标继续或安全重跑,临时文件未完成不能对用户可见。最终文件生成后写校验和与行数,下载时再次检查任务所有者和有效期。
导出字段要有明确口径
“订单金额”可能是 grand_total、base_grand_total、已付或已退款;“订单日期”也有数据库 UTC 与后台时区之分。优化前先固定列定义、时区、币种和状态范围,用一小批人工对账样本做基准。否则性能变快后仍可能交付无法使用的数据。
下载过程与生成过程分离
文件生成完成后再开放下载,避免浏览器断线导致任务重做。反向代理可负责受控传输,但授权应由应用先验证;文件名不能暴露客户信息。管理员取消任务时只终止未完成批次,并清理临时文件与任务锁。

