什么是闭包表?
闭包表 是一种用于在关系型数据库中高效表示和查询 树形层级结构(Tree / Hierarchy) 的设计模式。
它通过一张额外的“关系表”来显式存储 所有祖先(ancestor)与后代(descendant)之间的路径关系,包括自己到自己的关系。
为什么需要闭包表?
假设有如下分类结构:
|
|
传统做法(如邻接表模型)只存 parent_id
| id | name | parent_id |
|---|---|---|
| 1 | 电子产品 | NULL |
| 2 | 手机 | 1 |
| 3 | 智能手机 | 2 |
| 4 | 功能机 | 2 |
| 5 | 电脑 | 1 |
| 6 | 笔记本 | 5 |
这种设计在以下操作中效率很低:
- 查询“电子产品”下所有子分类(需递归多次查询);
- 判断“智能手机”是否属于“电子产品”(需向上遍历);
- 移动整个子树(如把“手机”移到“家电”下)逻辑复杂。
那采用路径枚举呢? 多存储一个 path
即每个节点保持从跟到自己的完整路径。
| id | name | parent_id | path |
|---|---|---|---|
| 1 | 电子产品 | NULL | “/1/” |
| 2 | 手机 | 1 | “/1/2/” |
| 3 | 智能手机 | 2 | “/1/2/3/” |
| 5 | 电脑 | 1 | “/1/5/” |
| 6 | 笔记本 | 5 | “/1/5/6/” |
如果子树移动,则相关的树结构都要变动,优点是可读性高,实现简单。
1. 查询某个分类的所有子分类(含多级)
查询所有后代不包含自己
|
|
适用场景:
- 树结构基本不变
- 层级路径直接用于业务逻辑展示,如面包屑导航
- 轻量级系统,单父节点
闭包表
新增一个表,记录所有祖先与后代之间的路径关系,包括自己到自己的关系。
| ancestor_id | descendant_id | depth |
|---|---|---|
| 1 | 1 | 0 |
| 1 | 2 | 1 |
| 1 | 3 | 2 |
| 1 | 4 | 2 |
| 1 | 5 | 1 |
| 1 | 6 | 2 |
| 2 | 2 | 0 |
| 2 | 3 | 1 |
| 2 | 4 | 1 |
| 3 | 3 | 0 |
| 4 | 4 | 0 |
| 5 | 5 | 0 |
| 5 | 6 | 1 |
| 6 | 6 | 0 |
1. 查询某个分类的所有子分类(含多级)
|
|
2. 查询某个分类的所有祖先(向上追溯)
|
|
3. 判断 A 是否是 B 的祖先
|
|
4. 获取直接子节点(depth = 1)
|
|
5. 移动子树(例如把“手机”移到“家电”下)
- 删除以“手机”及其后代为 descendant 的所有旧祖先关系;
- 插入新祖先(“家电”及其祖先)到“手机”子树的新路径。
适用场景:
- 商品分类系统
- 组织架构
- 评论嵌套(楼中楼)
- 文件夹/目录结构
- 权限菜单树
案例
背景知识
- 分类有父级子级
- 一个分类有多个资源
- 一个资源可以绑定多个分类
- 分类固定
解决方案
路径枚举 + 中间关联表
避免过度设计,中间关联表可以缓存 path,连表查询相关数据。