无限级分类应优先选用nestedset模型替代parent_id,整棵树一次查询并缓存,key为categories:full-tree:v2;若用parent_id则需分层缓存根节点、子节点及路径,统一使用带prefix和压缩的Redis驱动,并在模型事件中同步清理相关缓存。

无限级分类本身不是缓存问题,而是数据结构与查询方式问题;缓存只是后续性能优化的一环。直接对 parent_id 邻接表做“全树缓存”容易失效、体积大、更新难,必须结合分类模型特点设计缓存策略。
优先选型:用 nestedset 替代 parent_id + 缓存整棵树
如果项目允许重构,kalnoy/nestedset 是最稳妥的起点。它用 _lft/_rgt 字段实现 O(1) 子树查询,天然适合缓存:
- 整棵树只需一次 SQL 查询(
SELECT * FROM category ORDER BY _lft),结果可安全缓存数小时甚至一天 - 缓存 key 可设为
categories:full-tree:v2,版本号便于批量刷新 - 新增/移动节点时,调用
$node->save()后主动清除该 key:Cache::forget('categories:full-tree:v2') - 不依赖递归或 N+1,避免缓存污染和深度嵌套反序列化开销
若坚持用 parent_id 模型:缓存要分层、按需、带边界
不能缓存“整个分类树”,而应缓存高频、稳定、低更新频次的子集:
-
根级分类列表(如导航栏):缓存 1 小时,key 为
categories:roots,用Category::whereNull('parent_id')->get()查询 -
指定节点的直系子类(1 层):比如点击“电子”后展开一级子类,key 为
categories:children-of-5,过期时间 30 分钟 -
全路径字符串(用于面包屑):如
categories:path-for-42,值为["数码", "手机", "iPhone"],只在节点被修改时清除 - 所有缓存统一走 Redis 驱动,禁用 file 或 database —— 它们无法支撑树形结构的批量读写
缓存驱动配置要点(.env + config/cache.php)
确保生产环境使用 Redis 并启用压缩,避免大 value 占用过多内存:
-
CACHE_DRIVER=redis(绝不用 database 或 file) - 在
config/cache.php的stores.redis下添加:'options' => ['compression' => true] - 为分类缓存加统一 prefix,例如:
'prefix' => 'cat:',方便运维识别和清理 - 不要用
Cache::tags()做分类分组——除非你确认驱动是 Redis 且未混用 file
更新时缓存一致性处理
分类树更新比普通数据更敏感,必须同步清理相关缓存:
- 新增节点:清除父节点的 children 缓存 + 全树缓存(如有)
- 移动节点:清除原父节点、新父节点的 children 缓存,以及自身 path 缓存
- 删除节点:清除自身、所有后代的 path 缓存,以及上级的 children 缓存
- 推荐在模型的
saved和deleted事件中触发清理,而非控制器里零散写











