直接用db::insertall()最快,需分批(如400条/批)、手动加事务、关闭验证;saveall()因验证/事件开销大且不自动分片,易触发max_allowed_packet超限、内存溢出或超时。

直接用 saveAll() 或 Db::insertAll() 是最快的,但必须分批、加事务、关验证——不这么做,10 万条数据大概率卡死或报错。
为什么不能一次性 saveAll($data)
ThinkPHP 的 saveAll() 本质是拼一条大 SQL:INSERT INTO ... VALUES (),(),()...。它不自动分片,全量数组会一次性加载进内存,还可能触发 MySQL 的 max_allowed_packet 限制(默认 4MB),报错 “Packets larger than max_allowed_packet are not allowed”。另外,Web 请求超时(Nginx 默认 30 秒)、PHP 内存溢出(Allowed memory size exhausted)都是高频问题。
- 单次传入超过 500 条,风险陡增;超过 1000 条,基本不可靠
-
saveAll()默认走模型验证、时间戳填充、事件钩子,每条都跑一遍,纯浪费 CPU - 没包事务的话,中间失败会导致部分写入,数据不一致,且无法回滚
Db::insertAll() 比 saveAll() 更适合大批量
绕过模型层,不读配置、不初始化验证器、不触发事件,只做 SQL 拼接和执行。轻量、快、内存占用低,适合日志归档、Excel 导入等可信数据源场景。
- 字段名必须严格匹配数据库列名,
type映射、JSON 自动序列化等功能全部失效 -
create_time/update_time不会自动填充,得手动塞进每条数据里 - 返回值是影响行数(
int),不是 ID 列表,没法知道插入了哪些 ID - 支持第二个参数控制每批数量:
Db::name('user')->insertAll($data, 400),内部自动array_chunk并逐批执行
必须手动加事务 + 分批 + 清理内存
框架的 insertAll($data, $batchSize) 只负责切片执行,但不包事务。一旦某一批失败,前面几批已提交,无法回滚。CLI 脚本还要防内存泄漏。
- 用
Db::transaction(function () use ($chunks) { ... })包裹整个批次循环,异常自动回滚 - 别在循环里反复
new User(),复用一个模型实例:$user = new User(); $user->saveAll($chunk) - CLI 下每批执行完调
gc_collect_cycles(),尤其处理几十万条时,否则内存只增不减 - Web 环境别硬扛,改用队列(如 think-queue),把大任务拆成多个 job 异步执行
- 记得设
set_time_limit(0)(CLI)或调大max_execution_time(Web)
主键类型和引擎会影响返回值与性能
saveAll() 的返回值取决于主键类型:自增整型返回插入后的 ID 数组;UUID 或字符串主键只返回 true/false。InnoDB 表锁粒度比 MyISAM 细,但大事务仍可能引发锁等待。
- MyISAM 在批量插入时会锁整张表,高并发下极易阻塞;InnoDB 更稳妥,但要确保有合适索引支撑
WHERE或排序字段 - 如果表用 UUID 当主键,又没建聚簇索引,批量插入性能会明显下降(页分裂频繁)
- 用
Db::insertAll()时,若需返回 ID,只能自己查SELECT LAST_INSERT_ID(),但要注意它只返回第一批的起始 ID
真正难的不是“怎么插”,而是“插到一半崩了怎么办”——事务边界、错误恢复、内存水位、MySQL 配置(max_allowed_packet、innodb_log_file_size)这些细节漏掉一个,上线就出事。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











