是,createMany会触发模型事件和强制类型转换,因其本质是循环调用new static()+save(),走完整生命周期;而insert()和upsert()绕过模型层,不触发事件、不执行casts。

createMany 会触发模型事件和强制类型转换吗
会,createMany 是模型级别的批量操作,它本质是循环调用 new static() + save(),所以每个模型实例都会走完整的生命周期:填充属性、强制类型转换($casts)、触发 creating/created 事件、执行 boot 中的观察逻辑。
这意味着如果你有大量数据(比如 5000 条),又在 creating 里做了 DB 查询或 HTTP 调用,性能会断崖式下跌——不是慢一点,而是直接卡住。
- 想跳过事件?用
insert()或upsert(),它们绕过模型层 - 需要类型转换但不要事件?手动遍历数组,用
fill()+setAttribute()处理后再insert() -
createMany适合场景:数据量小(
insert() 和 createMany 性能差距有多大
实测插入 1000 条用户记录(含 5 个字段),createMany 平均耗时 850ms,insert() 是 42ms —— 差 20 倍。根本原因不在 SQL,而在 PHP 层:前者新建 1000 个模型对象、跑 1000 次构造函数和事件分发;后者只拼一条 SQL,一次 PDO 执行。
注意:insert() 不接受关联数组(如 ['name' => 'A']),只接受「二维索引数组」,且字段顺序必须和 insert() 第一个参数的键名严格一致:
User::insert([
['name' => 'Alice', 'email' => 'a@b.c', 'created_at' => now()],
['name' => 'Bob', 'email' => 'b@c.d', 'created_at' => now()],
]);
- 字段缺失?不会报错,但对应列写入
NULL或默认值(取决于表结构) - 时间戳不会自动填充,必须显式传
created_at/updated_at - 主键自增 ID 不会返回,
insert()只返回布尔值
upsert() 适合什么场景,为什么不能替代 createMany
upsert() 的核心价值是「存在则更新,不存在则插入」,底层用的是数据库的 INSERT ... ON CONFLICT(PostgreSQL)或 INSERT ... ON DUPLICATE KEY UPDATE(MySQL)。它不触发模型事件,也不走 $casts,但能保证唯一约束下的幂等性。
常见误用:拿它当「带去重的 createMany」。问题在于——
- 你得明确指定
$uniqueBy字段(比如['email']),否则冲突判断失效 - 更新字段只能是
$updateColumns列表里的,漏写就丢数据(例如没写'updated_at',时间戳永远不变) - 如果业务要求「重复时不更新任何字段,只跳过」,
upsert()无法做到,得先whereNotIn('email', [...])查一遍再插
简单说:upsert() 是「防重+轻量更新」,不是「createMany 的加速版」。
大数量插入时,事务和 chunk 怎么配
直接 DB::transaction() 包住 10000 条 insert() 很危险:内存爆、锁表久、失败后回滚成本高。正确做法是分块 + 手动事务:
User::upsert($chunk, ['email'], ['name', 'updated_at']);
- 每 chunk 控制在 100–500 行(MySQL 建议 ≤ 500,PostgreSQL 可到 1000)
- 别用模型的
chunkById()做插入,那是为「读取后处理」设计的;插入用collect($data)->chunk(500)更直接 - 如果必须用
createMany(比如要触发事件),单次绝不超过 50 条,否则事件开销压垮 PHP 内存
最常被忽略的一点:insert() 和 upsert() 都不校验模型的 $fillable,字段名写错只会静默失败或插进错误列——务必提前 array_keys() 核对字段名是否和数据库一致。











