fill()仅作用于单个模型实例,不能批量更新;createmany()和upsert()支持批量操作但跳过模型事件;需触发模型逻辑时应封装事务化循环+fill()+save()。

为什么 fill() 和 createMany() 不能混用
很多人试过对一个已存在的模型实例调用 fill() 后再用 save(),以为能批量更新多条记录 —— 实际上 fill() 只作用于单个模型实例,它不会自动遍历数组或触发批量 SQL。真正支持“批量填充+入库”的是 createMany()(用于插入)和 upsert()(用于插入或更新),但它们的前提是传入二维数组,且每项都需符合模型的 $fillable 定义。
-
fill()只接受一维数组,赋值后必须手动save(),仅影响当前实例 -
createMany()接收二维数组,内部调用insert(),跳过模型事件和访问器,不触发creating/created - 若想触发模型生命周期钩子(比如自动设置
created_at、加密字段),得用循环 +create(),但性能较差
如何安全地批量插入并保留模型逻辑
如果业务要求必须走模型构造逻辑(例如自动哈希密码、生成 UUID、强制设置 tenant_id),就不能直接用 createMany()。此时推荐封装一个静态方法,在循环中复用模型的 fill() + save(),但要加事务兜底:
// User.php 模型内
public static function insertWithLogic(array $data): int
{
DB::transaction(function () use ($data) {
foreach ($data as $row) {
$user = new static();
$user->fill($row); // 自动过滤 $fillable,触发 setAttribute 等
$user->save();
}
});
return count($data);
}
- 务必用
DB::transaction()包裹,避免部分写入成功导致数据不一致 - 不要用
new static($row)构造,因为构造函数不触发fill()的属性过滤和 mutator - 若数据量超 500 行,建议分块(
collect($data)->chunk(100)),防止内存溢出或超时
upsert() 填充时哪些字段会被忽略
upsert() 是 Laravel 8.2+ 提供的“存在则更新,不存在则插入”能力,但它对字段控制非常严格:只有出现在 $uniqueBy 参数中的字段才用于判断唯一性,其余字段默认全部参与 INSERT/UPDATE,除非显式排除。
- 被
$fillable限制的字段仍受保护 —— 如果某字段不在$fillable中,即使传入也会被丢弃 - 主键(如
id)若在$uniqueBy中,会被用于 WHERE 条件;若不在,可能被当成普通更新字段,导致意外覆盖 - 时间戳字段(
created_at/updated_at)不会自动填充,必须显式传入或在upsert()的$updateColumns中指定为DB::raw('NOW()') - 示例:
User::upsert($rows, ['email'], ['name', 'status'])表示:用email判重,冲突时只更新name和status,其他字段(如password)不会被修改
批量赋值失败却没报错?检查这三点
常见静默失败场景不是语法错误,而是配置或权限级问题:
-
$fillable数组为空或未定义 → 所有字段被拒绝,fill()后模型属性仍是 null,save()成功但写入空值 - 数据库字段类型不匹配(如 PHP 传字符串给 INT 列)→ MySQL 可能转成 0 或截断,无异常抛出,需开
STRICT_TRANS_TABLES模式捕获 - 模型启用了
timestamps = false,但数据里又含created_at→ 该字段被忽略,且不提示
最稳妥的调试方式是在填充前加一行:dd(array_keys($model->getFillable()), array_keys($data[0] ?? []));,确认键名完全对齐。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











