DB::table()->insert() 比循环 create() 快,因后者每次实例化模型并触发事件、转换、时间戳等开销,前者直接执行单条多值 INSERT;需注意字段名严格匹配、值提前格式化、分块事务控制及 upsert() 主键不可更新等细节。

DB::table()->insert() 为什么比循环 create() 快得多
因为 create() 每次都实例化一个 Eloquent 模型,触发 creating、created 事件,执行属性转换($casts)、时间戳自动填充、验证逻辑(哪怕没开验证),100 条就是 100 次对象创建 + 100 次 SQL 解析。而 DB::table('users')->insert($data) 直接拼一条多值 INSERT 语句发给数据库,无模型开销。
常见错误现象:SQLSTATE[HY000]: General error: 1366 Incorrect integer value —— 某个字段传了 null 或字符串,但数据库要求非空整数;或时间字段传了 Carbon 实例没转成字符串。
- 数据必须是二维关联数组,键名严格等于数据库字段名(
created_at不能写成created_at_time) - 所有值需提前格式化:
now()->toDateTimeString()、json_encode($arr)、布尔转0/1 - 不能漏掉
NOT NULL字段,除非数据库允许默认值 - 单次传入行数建议 ≤ 1000,否则可能触发
Packets larger than max_allowed_packet are not allowed
分块插入时 DB::transaction() 该包在哪一层
包在每个 chunk 内部,而不是整个数据集外层。包太宽会锁表太久、InnoDB redo log 写满、其他请求排队卡死,尤其 upsert() 还会加间隙锁(gap lock)。
使用场景:纯单表插入(如日志流水)通常不需要外层事务,Laravel 默认 auto-commit 更稳;只有 A 表插入 + B 表插入必须同时成功时,才需要事务保障原子性。
- 用
collect($data)->chunk(500)切分,再对每个$chunk单独调用DB::transaction(fn() => DB::table(...)->insert($chunk)) - 事务闭包内禁止调用任何 Eloquent 模型方法(如
User::find())、不触发事件、不加载关系 - 别在同一个事务里混用
insert()和update(),锁类型不同,死锁风险陡增 - 如果某批失败,只回滚当前 chunk,不影响后续批次
upsert() 报错 Column 'id' cannot be null 怎么修
这是 Laravel upsert() 的典型陷阱:第三个参数(更新字段列表)里误写了主键 id。MySQL 原生语法不允许在 ON DUPLICATE KEY UPDATE 中更新主键,会直接报错。
正确做法是把主键从更新字段中剔除,只留业务字段。且必须确认数据库表已在第二个参数指定的字段(如 email)上建了唯一索引,否则行为未定义——不是报错,而是退化为普通 INSERT,重复数据直接冲突失败。
- 检查
upsert()第二个参数:['email']对应的字段是否已建UNIQUE或PRIMARY KEY - 第三个参数只能是可更新字段,如
['name', 'updated_at'],绝对不要包含'id' - Laravel 8 及以下没有
upsert(),调用会抛BadMethodCallException,需升级或改用原生语句 - SQLite 不支持
upsert(),本地开发测试时容易忽略这点
时间戳和模型事件怎么手动补
DB::table()->insert() 不走模型,所以 created_at/updated_at 不会自动填,模型事件也不会触发。如果你依赖这些逻辑(比如缓存失效、日志记录),就得自己补,但要注意别引入额外性能损耗。
性能影响明显:在每行数组里手动加 'created_at' => now()->toDateTimeString() 几乎无开销;但若循环里调 Cache::forget() 或发 HTTP 请求,就又回到“伪批量”状态。
- 时间戳字段必须手动赋值,推荐统一用
now()->toDateTimeString(),避免时区不一致 - 如需触发日志或缓存更新,优先批量处理(如插入完再
Cache::tags(['user'])->flush()),而非逐行调用 - 临时禁用模型事件用
User::unsetEventDispatcher(),但仅适用于后续完全不依赖事件的场景;并发请求下不能复用同一模型类的事件调度器 - 别在插入前用
DB::statement('SET FOREIGN_KEY_CHECKS = 0'),除非你确定没外键依赖且处于维护窗口期
meta_info,代码里写成 metaInfo 就静默失败;json_encode() 没加 JSON_UNESCAPED_UNICODE,中文变 \uXXXX,查起来全是乱码。


















