应分批、包事务、处理主键冲突地使用insertAll()或saveAll():insertAll()快但无模型逻辑,saveAll()支持验证和钩子;二者均不支持跳过主键冲突,需用原生SQL解决;大批量须分片(200–500条/批)、显式事务与错误捕获。

直接用 insertAll() 或 saveAll(),别写循环;但必须分批、包事务、处理主键冲突,否则大概率报错或数据不一致。
insertAll() 和 saveAll() 到底该选哪个
看场景:要快、不走模型逻辑、不验证、不触发事件,就用 Db::insertAll();要自动时间戳、类型转换、验证、钩子,就用模型的 saveAll()。
-
insertAll()只拼 SQL,字段名必须和数据库列名完全一致,create_time这类字段得自己塞进数据里 -
saveAll()会读模型配置,支持自动写入时间戳、JSON 字段序列化、验证器(默认开启,可关:加['validate' => false]) - 两者都不支持跳过主键冲突——遇到重复主键或唯一索引,整个批次直接抛
SQLSTATE[23000]: Integrity constraint violation: 1062中断 - 返回值不同:
insertAll()返回影响行数(int),saveAll()主键自增时返回 ID 数组,否则返回布尔值
主键冲突了怎么办:不是所有“重复”都能靠 ignore 解决
insertAll() 底层是 INSERT INTO ... VALUES (), (),不带 IGNORE 或 ON DUPLICATE KEY UPDATE。想跳过重复行,不能靠框架参数,得换方式。
- 真要跳过:改用原生 SQL,比如
Db::execute('INSERT IGNORE INTO table (...) VALUES (...), (...)') - 想“存在则更新、不存在则插入”:用
replaceInto(),但它本质是先DELETE再INSERT,可能触发外键级联或重置自增 ID,慎用 - 想保留原记录并只更新部分字段:只能手写
ON DUPLICATE KEY UPDATE的原生语句,insertAll()不支持 - 别指望把重复数据从数组里提前去重——PHP 层判断成本高,且并发下仍有冲突风险
大批量写入必踩的三个坑
10 万条数据用单次 insertAll() 跑不通,不是框架问题,是 MySQL 和 PHP 的硬限制在起作用。
立即学习“PHP免费学习笔记(深入)”;
- MySQL 报
Packets larger than max_allowed_packet are not allowed?说明单条 SQL 太大,必须分片,推荐每批200–500条,用array_chunk($data, 400) - CLI 脚本跑一半内存爆了?每批执行后加
gc_collect_cycles(),避免对象引用堆积;Web 环境建议扔进队列,别硬扛超时 - 事务里混用
saveAll()和insertAll()?它们可能走不同数据库连接实例,事务不生效;统一用Db::transaction()包裹,且只用一种写法 - 没显式
try/catch?失败时只返回 0 或部分成功数,脚本继续跑,后续操作全乱套——务必用Db::transaction(function () use ($data) { ... }),异常自动回滚
updateAll() 不能做字段自增,别被名字骗了
updateAll() 是“批量设固定值”,不是“批量计算更新”。它把传入的数据当静态值处理,不解析字段表达式。
- 想实现
sort = sort + 1?不行。必须用Db::execute('UPDATE table SET sort = sort + 1 WHERE ...') - 传
['score' => 'score * 2']?结果是把字符串'score * 2'写进字段,不是计算 - where 条件传空数组或
null?条件会被静默忽略,不是报错,容易误以为“没更新”,其实是条件丢了 - 它也不支持关联模型更新,纯表级操作,别指望它走
belongsTo关系
真正麻烦的从来不是“怎么写”,而是“怎么不出错”——分片大小、事务边界、主键策略、错误捕获粒度,这些细节一漏,批量操作就从提效变成埋雷。尤其是线上导数据,宁可慢一点、多写几行控制逻辑,也别信“一次到位”。



















