大Excel导入必须分块读+事务批量入库:用setReadDataOnly(true)关闭样式,getRowIterator()每块读500行并unset,insertAll()单次≤500条且须Db::transaction包裹,字段需清洗标题、校验类型、转换日期,错误带行号返回。

大 Excel 导入必须分块读 + 分批 insertAll
直接 load() 整个 Excel 文件,1 万行就可能爆内存。PhpSpreadsheet 默认会加载样式、公式、合并单元格等冗余信息,PHP 进程轻易吃光 512MB 内存。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 用
Xlsx或XlsReader 实例后,立刻调用setReadDataOnly(true)关闭样式解析 - 用
getRowIterator($start, $chunkSize)控制每次只读 500 行,别用toArray()—— 它等于放弃分块 - 每块处理完立即
unset($row)和unset($cell),避免 PHP GC 滞后导致内存不释放 - 每块数据组装成二维数组后,传给
Db::name('table')->insertAll($chunkData),单次不超过 500 条
事务包裹是底线,不是可选项
没包事务的 insertAll() 就是“赌运气”:中间某条失败,前面的已写入,后面不再执行,最终数据残缺且无法回滚。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 必须用
Db::transaction(function () use ($chunks) { ... })包裹整批处理逻辑 - 别在事务里混用
saveAll()和Db::insertAll(),它们可能走不同数据库连接实例,事务失效 - 事务内禁止发 HTTP 请求、调外部 API、写日志文件 —— 任何耗时操作都会拉长锁表时间
- 捕获
Throwable后必须重新 throw,否则框架不会自动回滚
字段清洗和类型转换不能跳过
用户上传的 Excel 标题常含换行符、全角空格;数字列被 Excel 自动转成 1.23E+10;日期显示为 44562 这类序列值;空单元格实际是 '' 或 null 或空白字符串。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 标题行用
$worksheet->getCellByColumnAndRow($col, 1)->getValue()取值,再trim(str_replace(["\n","\r"], "", $header))清洗 - 数值列优先看
getDataType():若为Cell::TYPE_NUMERIC且含小数点,用getFormattedValue()防科学计数法失真 - 日期列必须用
\PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($val)转,别用date('Y-m-d', $val * 86400)硬算 - 空值统一转
null(对应数据库允许 NULL 的字段),int 类型字段塞''会触发 MySQL 强制转 0,埋下业务隐患
重复校验得手动查库,validate 规则不管用
unique 验证规则在 saveAll() 或 insertAll() 中根本不会触发数据库查询 —— 它只在单条 save() 时生效。批量下,你指望的“自动去重”实际不存在。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 提前提取待入库的所有校验字段组合,比如
array_map(fn($x) => [$x['order_id'], $x['sku_id']], $data) - 用
Db::name('table')->where('order_id', 'in', array_column($data, 'order_id'))->select()查出库里已存在的记录 - 把库里结果也转成相同结构的键数组,用
array_intersect()找出重复项 - 返回错误时绑定原始行号,例如
['row' => 1234, 'reason' => 'order_id+sku_id 已存在'],前端才好定位
真正卡住人的从来不是语法,而是 Excel 标题里那个看不见的全角空格、MySQL 的 max_allowed_packet 默认值、或者事务里悄悄执行的一次 Redis 写操作。这些点不提前踩一遍,上线后只能靠日志一行行翻。



















