导出excel必须剥离分页逻辑,使用cursor()或chunk()处理全量数据并严格隔离参数。需禁用phpspreadsheet缓存、调用garbagecollect(),优先选用csv流式导出,确保utf-8编码与响应头正确设置。

导出Excel时不能复用分页SQL,否则只导出当前页那十几条——这不是“导出当前页”,是逻辑错位。
导出接口必须剥离 LIMIT 和 OFFSET
分页用的 paginate() 或手写 limit/offset 是为渲染列表服务的,导出要的是符合条件的全量数据(或带兜底限制的全量)。直接把 $_GET['page'] 和 $_GET['per_page'] 塞进导出逻辑,结果文件里就真只有 20 行。
- 从控制器入口识别导出请求:比如检测
$_GET['export'] === 'csv'或$_GET['export'] === 'xlsx',而非依赖分页参数 - 构造查询时,先统一拼 WHERE 条件(含搜索词、状态筛选、时间范围等),导出路径里不追加
limit,分页路径里才加 - 加数量兜底:查总数
$total = Db::table('user')->where($where)->count(),若超 10000 就中止,返回提示
大数据量导出必须用 cursor() 或 chunk(),别碰 select()
Db::table()->where($where)->select() 会把全部结果加载进内存,10 万行起步就 OOM。ThinkPHP 的 Collection 对象还有额外引用和序列化开销。
- 优先用
cursor():返回 PDOStatement,逐行fetch(),内存恒定。注意需在数据库配置中关闭缓冲:'pdo_attr' => [PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false] - 需要中间计算(如关联字典、格式转换)时,改用
chunk(500, function ($batch) { ... }),每块处理完自动释放内存 - 绝对避免在循环里反复调
Db::table()->where(...)->find()——单条查 10 万次,比chunk()慢一个数量级
PhpSpreadsheet 写 Excel 时内存暴涨?清理和缓存是关键
每调一次 $sheet->setCellValue() 都新建 Cell 对象,PhpSpreadsheet 默认开启内存缓存,不手动干预,第二块开始写入速度断崖下跌。
- 每
chunk处理完后,必须调$spreadsheet->garbageCollect(),强制释放未引用的 Cell / Style 对象 - 初始化时禁用缓存:
Settings::setCache('none')或创建时传参['cache' => 'none'] - 不要在循环里反复
createSheet()或clone $sheet,单文件导出只用一个Worksheet实例
用户只要数据,CSV 比 XLSX 更务实
如果不需要合并单元格、颜色、公式,CSV 是更优解:它本质是文本流式写入,和数据库游标天然契合,内存占用基本恒定,且无需任何第三方库。
- 用
fopen('php://output', 'w')直接输出,配合fputcsv($fp, $row, ',', '"')自动转义逗号、换行、双引号 - 响应头必须设对:
Content-Type: text/csv; charset=utf-8+Content-Disposition: attachment; filename="data.csv" - 中文字段不用手动转编码,只要确保数据库连接、PHP 脚本、输出流三者都是 UTF-8
最容易被忽略的是:导出前没做参数隔离,导致用户篡改 URL 中的 keyword 或 status 就能导出完全不同的数据集;还有就是游标查询中途混用了 ThinkPHP 的链式查询方法,PDO 属性重置,游标直接失效——这两处一出问题,前面所有内存优化都白搭。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











