应根据是否需要模型层逻辑选择:需验证、事件、自动时间戳时用saveAll(),纯数据搬运且字段已对齐时用insertAll();前者返回ID数组,后者仅返回影响行数,且均不支持自动类型转换。

saveAll() 和 insertAll() 到底该用哪个
看你要不要模型层逻辑:需要验证、事件、自动时间戳,就用 saveAll();只是搬数据、字段已对齐、不走模型,直接上 Db::name('table')->insertAll($data)。
常见错误是混用场景:insertAll() 里传了带 id 的数组但表没建唯一索引,导致重复插入失败;saveAll() 里传了非法字段名,触发验证直接中断。
-
saveAll()返回自增主键 ID 数组(如果主键是自增整型),insertAll()只返回影响行数int -
saveAll()会读模型配置、跑before_write钩子、走验证器;可信数据源加['validate' => false]能提速 30%+ -
insertAll()不查模型、不初始化验证器,内存低,适合日志归档、后台导入 - 两者都不支持自动类型转换(比如把字符串
'2026-04-24'转成日期),字段值必须是数据库直接受纳的格式
为什么 insertAll() 还是慢?事务没包住
批量写入中途出错,默认是“前面成功、后面失败”,数据残缺且无法回滚。不加事务的 insertAll() 就是埋雷。
别手写 Db::startTrans() + foreach + insertAll() + Db::commit() —— 容易漏异常捕获或忘记 rollback()。
- 正确姿势只有一种:
Db::transaction(function () use ($chunks) { foreach ($chunks as $chunk) { Db::name('user')->insertAll($chunk); } });,异常时自动回滚 - 别在事务里混用
saveAll()和Db::insertAll(),它们底层连接可能不同,事务不生效 - 别在事务里调外部 API、发邮件、写文件——锁表时间拉长,高并发下极易阻塞其他请求
单次插多少条才安全
别信“越多越好”。MySQL 默认 max_allowed_packet=4M,按平均每行 200 字节算,500 条就逼近极限;PHP CLI 下内存也容易爆。
超量典型报错:Packets larger than max_allowed_packet are not allowed,或 PHP 报 Allowed memory size exhausted。
- 实测稳妥区间是 200–400 条/批,用
array_chunk($data, 400)主动切片 - Web 请求中严禁一次性处理 >1 万条——Nginx 默认超时 30 秒,应改用队列(如
think-worker) - CLI 脚本记得加
set_time_limit(0)和gc_collect_cycles(),尤其循环处理多批次时
insertAll() 已经很快了,但还能再快吗
当 insertAll() 还不够快(比如单表插入 1w+ 行),就得跳过框架封装,手写 SQL 并显式控制事务。
TP 默认每条 insertAll() 都自动开启事务,但手动合并能减少事务头尾开销,性能快 3–5 倍。
- 先关自动提交:
Db::startTrans() - 拼接 SQL:
INSERT INTO user (id,name) VALUES (1,'a'),(2,'b'),(3,'c')—— 注意括号间逗号,末尾不加 - 用
Db::execute()执行,别用query()(后者可能被拦截重写) - 出错时
Db::rollback(),成功后Db::commit()
MySQL 的 innodb_buffer_pool_size 和 bulk_insert_buffer_size 会影响大批量插入吞吐,但 TP 层完全感知不到。线上环境若没调优,即使写了最优 SQL,也可能卡在磁盘刷写上。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











