常见的实现方案及区别
1. 邻接表(Adjacency List)
方案描述:
-
每条记录存储一个节点的父节点ID。
-
表结构大致:
id INT PRIMARY KEY, name VARCHAR(...), parent_id INT -- 指向父节点的ID,根节点为NULL或0
优点:
-
结构简单,直观,容易维护。
-
插入、删除单条节点简单。
缺点:
-
查询整个树或任意节点的所有子孙节点比较复杂,需递归多次查询(MySQL 8.0之前不支持递归CTE,查询性能低)。
-
需要递归处理。
2. 路径枚举(Path Enumeration)
方案描述:
-
每条记录保存从根节点到当前节点的完整路径。
-
例如路径字段
path
存储为/1/5/9/
。 -
表结构大致:
id INT PRIMARY KEY, name VARCHAR(...), path VARCHAR(...) -- 存储路径字符串
优点:
-
查询所有子孙节点通过
LIKE 'path%'
即可,查询简单。 -
不用递归,查询效率较高。
缺点:
-
修改节点路径(如移动节点)时,需要更新所有子节点路径,操作复杂且成本高。
-
路径字段存储占用空间较大。
3. 嵌套集合模型(Nested Set Model)
方案描述:
-
利用左右值(left, right)表示节点的范围区间。
-
每个节点有两个整数值
lft
和rgt
,表示在树中的先序遍历位置。 -
表结构大致:
id INT PRIMARY KEY, name VARCHAR(...), lft INT, rgt INT
优点:
-
查询子孙节点非常快(单次范围查询)。
-
适合大量读,查询频繁的场景。
缺点:
-
插入、删除、移动节点时,需要调整大量节点的
lft
和rgt
值,操作复杂。 -
维护成本高。
4. 闭包表(Closure Table)
方案描述:
-
额外建一个关联表,存储节点之间的所有祖先-后代关系。
-
关联表结构:
ancestor_id INT, descendant_id INT, depth INT -- 距离,0表示自身
优点:
-
查询任意节点的所有子孙和所有祖先非常方便且性能好。
-
插入删除只需要维护关联表,灵活。
缺点:
-
需要额外的存储空间。
-
维护关联表的操作相对复杂。
5. MySQL 8.0+ 的递归公共表表达式(CTE)
-
MySQL 8.0支持递归CTE,邻接表查询树形数据变得更简单。
-
利用递归CTE实现递归查询,无需额外结构。
总结对比
方案 | 查询复杂度 | 插入/更新复杂度 | 存储成本 | 适用场景 |
---|---|---|---|---|
邻接表 | 递归查询,慢 | 简单 | 低 | 数据结构简单,更新频繁 |
路径枚举 | 简单LIKE查询 | 复杂,需更新路径 | 中等 | 读多写少,结构变化不频繁 |
嵌套集合 | 简单范围查询 | 复杂,调整左右值 | 低 | 读多写少,查询性能要求高 |
闭包表 | 简单且灵活 | 维护关联表复杂 | 高 | 需要频繁查询任意层级关系 |
递归CTE(MySQL8+) | 简单递归查询 | 简单 | 低 | 适合任意层级递归查询,无需额外表 |