ThinkPHP循环分批写入不超内存的核心是避免数据在内存堆积,关键三步:切片加载(array_chunk)、原生写入(Db::insertAll)、及时释放(unset+gc_collect_cycles)。

ThinkPHP 循环分批写入不超内存,核心不是“控制循环次数”,而是“不让数据在内存里堆积”。关键动作就三步:切片加载、原生写入、及时释放。
用 array_chunk 切成小批次
别把全部数据一次性读进数组再传给 insertAll。比如有 50 万条记录,先按 400 条一批切分:
- 每批 200–500 条较稳妥,字段少、索引简单可选 500;含 JSON 或多文本字段建议压到 300
- 用
array_chunk($data, 400)拆分,不是靠框架自动分,TP8 不会帮你 chunk - 如果数据来自 Excel,直接用 Spout 流式读取,边读边 chunk,内存恒定在 3–5MB
用 Db::insertAll 走原生 SQL
绕过模型层,避免验证、事件、时间戳、对象实例化等开销:
- 不用 saveAll(),它每条都 new Model,10 万条就是 10 万个对象引用
-
字段名必须和数据库列严格一致,比如
create_time不会自动填充,得自己塞进去 - json 字段提前
json_encode(),insertAll 不做序列化 - 返回值是影响行数(int),不是 ID 列表,别指望回填主键
每批后手动清理 + 控制执行环境
光分批还不够,PHP 引用不释放,内存照样涨:
立即学习“PHP免费学习笔记(深入)”;
- 每批插入完立刻
unset($chunk),再调gc_collect_cycles() - CLI 脚本开头加
set_time_limit(0),Web 请求别硬扛,改用队列或前端分片上传 - 关闭调试:
app_debug = false,删掉所有dump()和Log::debug($hugeData) - 事务按批包裹:
Db::transaction(fn() => Db::insertAll($chunk)),别用一个事务包全部
超百万行时考虑直连数据库导入
当数据量达百万级,连 insertAll 都可能触碰 MySQL 的 max_allowed_packet 限制(默认 4MB):
- 导出为 CSV,用
LOAD DATA INFILE(MySQL)或COPY(PostgreSQL),完全不走 PHP 内存 - 若必须 PHP 控制,拼接
INSERT INTO ... VALUES (),(),(),单条 SQL 控制在 ≤ 1000 行,注意 PDO 绑定参数上限 - 连接配置中设
'persistent' => false,防止连接资源滞留不释放



















