删除无限级分类节点应避免手动递归,优先用MySQL 8.0+的WITH RECURSIVE一次性查出子树ID再批量删;或用nestedset包自动处理;外键级联仅限直接子级,且须注意nullable、N+1、软删除和事务安全。

删除无限级分类节点时同步删掉所有子分类,关键不在“怎么递归调用”,而在避免手动递归、防止 N+1 或爆栈、保证事务安全。Laravel 10.x 本身不提供开箱即用的无限级删除支持,必须结合数据结构选对方案。
用 parent_id 实现时:先查 ID 树,再批量删
这是最稳妥、兼容性最强的做法(无需额外包),适合中小项目:
- 不要写递归作用域(如
scopeWithAllChildren()),容易漏终止条件或查出重复 ID - 改用数据库原生递归查询一次性拿到整棵子树 ID 列表(MySQL 8.0+ 支持
WITH RECURSIVE) - 示例迁移后,Category 表含
parent_id字段且允许为null - 删除逻辑分两步:
① 执行原生 SQL 获取目标节点及其全部后代 ID;
② 用whereIn('id', $ids)->delete()一次性物理删除
用 nestedset(kalnoy/nestedset)时:调用内置 delete() 即可
如果你已安装并配置好 kalnoy/nestedset,它会自动维护左右值(_lft/_rgt),删除节点时子树会被整体移除:
- 确保模型使用了
NodeTrait,且迁移中调用了NestedSet::columns($table) - 直接调用
$node->delete(),底层会自动执行范围删除(WHERE_lft >= ? AND _rgt ) - 该操作是原子的,也支持事务回滚,比手写递归更可靠
外键级联删除(慎用)
仅当子表明确依赖父表、且业务允许数据库强约束时才考虑:
- 在迁移中定义外键时加
onDelete('cascade'),例如:$table->foreignId('category_id')->constrained()->onDelete('cascade'); - 注意:这只能处理「直接子级」,无法自动穿透多层(比如子分类下还有孙分类)
- 若子分类表自身也有
parent_id,级联不会递归触发,仍需应用层处理
别踩这些坑
常见翻车点往往藏在细节里:
-
parent_id字段没设nullable()→ 根节点插不进,后续删除逻辑全乱 - 用
with('children')加载后再 PHP 递归删 → 触发 N+1 查询,数据量大时超时或内存溢出 - 软删除(SoftDeletes)混用 →
delete()变成更新deleted_at,子记录没被清理,造成逻辑残留 - 没加数据库事务 → 删除中途失败,出现父删了、子没删的不一致状态


















