Laravel 11批量导入无限级分类需先插入根节点再逐层递归插入子节点,用Category::insert()或DB::table()->insert()避免N+1和内存溢出,每批≤500条,结合upsert()处理重复,推荐通过Artisan命令流式导入并事务分片。

在 Laravel 11.x 中批量导入无限级分类数据,核心是解决两个问题:一是保持父子层级关系(parent_id 正确绑定),二是避免 N+1 或重复查询、事务卡顿、内存溢出等常见陷阱。不能简单用 create() 循环插入,也不宜直接递归调用 save() —— 那样会严重拖慢速度且不可控。
准备支持递归结构的数据表与模型
确保迁移文件中包含可空的 parent_id 字段,并建立外键约束:
$table->foreignId('parent_id')->nullable()->constrained('categories')->nullOnDelete();- 模型中设置白名单:
protected $fillable = ['name', 'parent_id']; - 定义基础关联(非递归作用域):
public function children() { return $this->hasMany(Category::class, 'parent_id'); }public function parent() { return $this->belongsTo(Category::class, 'parent_id'); }
用递归函数批量插入嵌套数组
假设你有一份 JSON 或 PHP 数组格式的树形分类数据(如菜单、商品类目),结构类似:
[<br> ["name" => "电子产品", "children" => [<br> ["name" => "手机", "children" => [["name" => "iPhone"]]],<br> ["name" => "电脑"]<br> ]]<br>]
推荐封装为服务类(如 CategoryImportService),按层级顺序插入并缓存已入库 ID:
- 根节点先批量插入(
parent_id为null),获取所有 ID 和插入顺序映射 - 对每个子层级,递归调用时传入父节点 ID;子数组插入后,用
DB::table()->insert()批量写入,并记录新 ID 映射到原始数组索引 - 避免 Eloquent 模型事件开销,用
Category::insert($rows)或原生DB::table('categories')->insert() - 每批控制在 500 行以内,超量时用
collect($data)->chunk(500)分片
用 upsert() 处理重复或更新场景
如果导入数据可能含已有分类(比如按 code 或 slug 去重),需提前加唯一索引:
Schema::table('categories', function (Blueprint $table) { $table->unique('slug'); });- 构造带业务唯一键(如
slug)和更新字段(如name,parent_id)的数据数组 - 调用:
Category::upsert($data, ['slug'], ['name', 'parent_id', 'updated_at']); - 注意:Laravel 11 默认支持 MySQL 8.0.19+ 的
ON DUPLICATE KEY UPDATE,无需额外适配
命令行导入 + 流式处理更稳妥
Web 请求不适合百万级分类导入。应新建 Artisan 命令(php artisan import:categories):
- 接收 JSON 文件路径参数:
$input = $this->argument('file'); - 用
json_decode(file_get_contents($input), true)加载,不走 HTTP body 解析 - 关闭查询日志:
DB::disableQueryLog();防止内存撑爆 - 关键操作加
DB::transaction()包裹每个 chunk,而非整个导入过程 - 导入完成后可触发缓存刷新或重建侧边栏菜单视图











