批量插入应优先使用 saveall() 或 db::insertall(),避免手写循环;需分批(200–500 条)、事务包裹、关闭验证(若可信)、注意主键类型与引擎要求。

批量插入用 saveAll(),别手写循环
ThinkPHP 的模型批量插入核心就是 saveAll(),它内部会自动拼接单条 INSERT INTO ... VALUES (),(),() 语句,比逐条 save() 快一个数量级。手写 foreach + save() 是最常见性能陷阱——每条都触发一次 SQL 查询、一次事务判断、一次验证逻辑。
- 必须传入二维数组,每个子数组是一条记录,键名需与数据库字段一致(或通过
field显式映射) - 默认开启数据验证,若确定数据已清洗干净,加参数
['validate' => false]可跳过,提速明显 - 单次不宜超过 500 条,MySQL 默认
max_allowed_packet容易报错Packets larger than max_allowed_packet are not allowed - 如果主键是自增整型,
saveAll()返回的是插入后的 ID 数组;如果是 UUID 或其他非自增主键,返回布尔值
Db::insertAll() 绕过模型更轻量
不需要模型事件、不需要验证、不走钩子函数时,直接用数据库查询类的 insertAll(),它只做纯 SQL 拼接和执行,内存占用低、启动开销小,适合日志归档、离线导入等场景。
- 参数是二维数组,但字段名必须严格匹配数据库列名(模型里的
type映射、json类型自动序列化等功能全部失效) - 不支持自动时间戳写入(
create_time/update_time不会自动填充),得自己塞进数据里 - 返回值是影响行数(int),不是 ID 列表,无法得知具体插入了哪些 ID
- 在高并发写入时,比
saveAll()更少锁表风险,因为不读取模型配置、不初始化验证器
大批次分段写入,避免超时和内存溢出
一次性处理 10 万条数据,哪怕用 insertAll() 也容易触发 PHP 内存限制或 MySQL 连接超时。必须主动切片,而不是依赖框架自动处理。
- 推荐每批 200–500 条,用
array_chunk($data, 400)拆分 - 每批执行后调用
gc_collect_cycles(),尤其在 CLI 环境下防止内存持续上涨 - CLI 脚本记得设
set_time_limit(0),Web 请求则建议改用队列,否则 Nginx/Apache 可能 30 秒就断连 - 不要在循环里反复 new 模型实例,复用同一个模型对象(如
$user = new User(); $user->saveAll($chunk))
事务包裹 + 异常捕获是硬要求
批量写入一旦中途失败,默认是部分成功,很难回滚。必须显式开启事务,否则数据一致性无法保障。
- 用
Db::transaction(function () use ($data) { ... })最稳妥,异常自动回滚 - 别在事务里混用
saveAll()和Db::insertAll(),它们底层连接可能不同,事务不生效 - 如果用了软删除字段(
delete_time),确保这批数据里没有该字段,否则saveAll()可能误判为“更新”而非“插入” - MySQL 引擎必须是 InnoDB,MyISAM 不支持事务,
START TRANSACTION会静默失效
批量插入真正的复杂点不在语法,而在于边界控制:数据来源是否可信、内存是否可控、事务是否真生效、错误后能否定位到第几条失败。这些没法靠一个函数自动兜底,得每一层都亲手卡住。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











