优先用db::insertall(高性能、分批、replace模式)或model::saveall(带验证/事件);禁用已废弃的addall;防重须靠唯一索引+异常捕获,而非应用层判断。

ThinkPHP 同时插入多条数据,优先用 insertAll,模型层用 saveAll;别用 addAll(TP3.2 专属,TP5+ 已废弃)。
Db 类的 insertAll 是最直接、最可控的批量插入方式
它绕过模型逻辑,直连数据库执行单条 INSERT ... VALUES (...), (...), (...) 语句,性能高、兼容性好,适合导入、同步等场景。
- 必须确保所有数组项字段名完全一致,否则 MySQL 报错
Column count doesn't match value count - 不触发模型事件(
before_insert/after_insert)、不走验证器、不自动处理时间戳或默认值 - 支持分批:第二个参数传入整数(如
100),超量时自动拆成多条 INSERT 执行,避免 MySQL 的max_allowed_packet限制 - MySQL 下可启用 replace 模式(第三个参数设为
true),实现“存在则替换”,但注意主键/唯一索引冲突时会删再插
示例:
$data = [
['name' => '张三', 'email' => 'z3@example.com'],
['name' => '李四', 'email' => 'l4@example.com'],
];
$result = Db::name('user')->insertAll($data, 100); // 分批,每批最多 100 条
模型类的 saveAll 适合需要校验和事件的业务插入
它本质是循环调用每个数据项的 save(),所以能走验证器、触发模型事件、自动填充 create_time 等字段,但性能明显低于 insertAll。
- 数据必须是二维数组,且每项需能实例化为该模型对象(字段名需与模型定义或数据库列匹配)
- 任意一条失败,整个操作中断,返回
false;不会部分成功 - 不支持原生分批,大数据量时建议自己切片 + 循环调用
saveAll($chunk) - TP8 中已明确不推荐在高并发写入场景使用,因其无法保证原子性(无事务包裹默认)
示例:
$users = [
['name' => '王五', 'email' => 'w5@example.com'],
['name' => '赵六', 'email' => 'z6@example.com'],
];
$result = User::saveAll($users); // 触发验证、事件、自动时间戳
为什么不能用 addAll?
addAll 是 ThinkPHP 3.2 的遗留方法,仅存在于 M() 实例中,TP5.0 起已移除,TP6/8 中调用会报 Call to undefined method 错误。
- 即使你在旧项目里看到它,也别复刻到新项目 —— 它底层也是拼 SQL,但不支持分批、无 replace 模式、字段容错差
- TP3.2 文档明确标注仅支持 MySQL,其他驱动(如 PgSQL、SQLite)直接不可用
- 官方迁移指南要求所有
addAll调用必须改为Db::insertAll或Model::saveAll
高并发下重复插入的坑,insertAll 本身不解决
批量插入 ≠ 并发安全。如果你的业务逻辑是「先查是否存在,再 insertAll」,那在并发请求下依然可能写入重复数据 —— 因为查和插之间有竞态窗口。
- 真正防重靠数据库约束:给关键字段(如
openid、sn)加UNIQUE索引,然后捕获Integrity constraint violation异常做降级处理 - 不要依赖应用层判断;也不要试图用锁表或 Redis 分布式锁来保护
insertAll,这反而扼杀批量优势 - 如果业务强依赖「不存在才插入」,且无法加唯一索引,应改用
INSERT IGNORE或ON DUPLICATE KEY UPDATE,但 TP 原生insertAll不直接支持,需用Db::execute()手写 SQL
最后提醒一句:批量插入的字段一致性、数据库包大小限制、以及并发下的唯一性保障,这三点漏掉任一,线上就容易出静默失败或脏数据。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











