在SQL中处理树和层次结构是一个常见的用例:产品目录、组织结构、菜单。最初,大多数数据库管理系统只支持对父节点的引用(ParentId),但随着时间的推移,出现了替代方法——嵌套集合和路径(Path),以及通过CTE的递归查询。
传统父子方法的问题是:在遍历大型层次结构时速度很慢;深度超过2-3的选择会导致JOIN的数量急剧增加。更复杂的技术(嵌套集合、路径枚举)加速了大规模遍历,但使得插入/删除变得困难。
解决方案是根据特定任务选择最佳存储模型:
通过CTE进行子树的递归查找示例:
WITH RecursiveTree AS ( SELECT id, parent_id, name, 0 as level FROM categories WHERE parent_id IS NULL UNION ALL SELECT c.id, c.parent_id, c.name, rt.level + 1 FROM categories c INNER JOIN RecursiveTree rt ON c.parent_id = rt.id ) SELECT * FROM RecursiveTree;
关键特性:
如果深度固定,是否可以用一个去规范化的表来替代树的存储,且只有一层引用?
只有在深度很小且始终固定(2-3层)的情况下——否则JOIN变得不可控,处理更改会变得复杂。
在大型树中,CTE不会“卡住”吗——是否存在对栈、递归深度的限制?
是的,大多数数据库管理系统对递归深度设置了限制(例如,100/32767)。对于1000+层的树,需要手动管理深度或使用自定义的遍历/分割算法。
嵌套集合是所有问题的解决方案吗?
不是,嵌套集合可以瞬间查找所有后代,但插入/删除需要对所有左/右索引进行大规模更新——在频繁更改的情况下,这会很慢。
插入示例(简化):
UPDATE tree SET lft = lft + 2 WHERE lft > @parent_right; UPDATE tree SET rgt = rgt + 2 WHERE rgt >= @parent_right; INSERT INTO tree(id, name, lft, rgt) VALUES(@new_id, @name, @parent_right, @parent_right + 1);
大型在线商店在邻接列表中存储目录树,并通过嵌套子查询构建菜单。在6层以上的菜单中,主查询需要几分钟才能执行,而且对树的任何更改都会导致级联更新。 优点:
缺点:
转向使用嵌套集合来处理静态树(类别),而将菜单路径转向使用路径。对于动态场景,使用CTE。查找后代变得瞬间完成,变化以批处理形式进行。 优点:
缺点: