DB::table('xxx')->insert()需传入键名严格匹配字段的二维关联数组,手动补全时间戳等字段,单次≤1000行并分块处理,避免混用模型逻辑。

直接用 DB::table('xxx')->insert(),别碰 createMany() 或循环 save() —— 后两者在批量场景下性能断崖式下跌,且极易触发内存溢出或 MySQL 包大小限制。
DB::table()->insert() 怎么写才不报错
这是最常用也最容易翻车的写法。核心是数据结构必须严格匹配字段名,且不能混入模型层逻辑。
- 二维关联数组,每个子数组键名必须和数据库字段名完全一致(比如
created_at不能写成created_at_timestamp) - 所有值必须已格式化好:时间用
now()->toDateTimeString(),布尔转0/1,JSON 字段提前json_encode() - 不能包含未定义字段,也不能漏掉非空字段(除非数据库允许 NULL)
- 单次传入行数建议 ≤ 1000;超量必须用
collect($data)->chunk(500)切分,否则可能触发Packets larger than max_allowed_packet are not allowed
示例:
DB::table('users')->insert([
['name' => 'Alice', 'email' => 'a@example.com', 'created_at' => now()->toDateTimeString()],
['name' => 'Bob', 'email' => 'b@example.com', 'created_at' => now()->toDateTimeString()],
]);
insert() 和 upsert() 到底该选哪个
不是看“哪个更快”,而是看“数据会不会重复”。选错会导致静默失败、主键冲突报错,甚至脏数据。
- 确定无重复(如日志、埋点、导入原始流水)→ 用
insert(),最快最轻量 - 有业务唯一标识(如
email、sku、phone),且需幂等(存在就跳过/更新)→ 必须先建唯一索引,再用upsert() -
upsert()第二个参数($uniqueBy)漏写,行为退化为普通insert(),不会报错但去重失效 - MySQL 5.7 及更早版本不支持
upsert(),会抛NotSupportedException;Laravel 8 及以下需手动写原生语句
示例(MySQL):
DB::table('users')->upsert(
[['email' => 'a@example.com', 'name' => 'Alice', 'updated_at' => now()]],
['email'],
['name', 'updated_at']
);
为什么 DB::transaction() 包整个大批次反而更慢
事务不是万能加速器。包得太宽,锁表时间长、InnoDB redo log 容易写满、其他请求被堵死,尤其在 upsert() 场景下会加间隙锁(gap lock)。
- 单表纯插入(无关联、无更新)→ 不需要外层事务,Laravel 默认 auto-commit 更稳
- 需要原子性(如 A 表插入 + B 表插入必须同时成功)→ 仅对每个 chunk 加事务,而不是把 10 万行塞进一个事务
- 每批行数 > 500 就要警惕:InnoDB 日志可能强制刷盘,导致卡顿
- 别在同一个事务里混用
insert()和update(),死锁风险陡增
createMany() 为什么是最慢的选择
它看起来最“Laravel”,实则是性能黑洞:每行都实例化 Model,触发全部 booting/creating 回调,执行 $casts、mutators、验证(哪怕你关了 $guarded),还做属性填充和关系初始化。
- 1 万条数据,
createMany()比DB::table()->insert()慢 3–5 倍,内存占用高 2 倍以上 - 它不做任何分块,大数据量下一样撞
max_allowed_packet - 如果你真需要模型逻辑(如自动填充时间戳),改用
Model::upsert(),它至少跳过实例化开销 - 字段含 JSON 或长文本时,
createMany()的序列化/反序列化开销会进一步放大
真正需要控制细节的地方,往往藏在 chunk 大小、唯一索引是否建对、PDO 是否启用原生预处理这些地方,而不是换一个更“高级”的方法名。


















