MySQL 嵌套集合模型:利用 lft/rght 字段高效查询树形结构

键盘旁的猫
键盘旁的猫
发布时间:2026-10-13| 3人浏览过
原创

在处理系统权限、分类目录等具有父子层级的数据时,传统的主外键自关联查询递归性能较差。本文介绍 MySQL 中的嵌套集合模型(Nested Set Model),通过为每个节点记录“左值”(lft)和“右值”(rght),将复杂的树形遍历转化为简单的区间判断查询。内容包括核心原理图解、查子/查父 SQL 写法、深度计算以及动态插入节点的操作流程。

嵌套集合模型原理

嵌套集合模型的核心思想是对树进行“后续遍历”,并为每个节点分配两个整数值:表示进入该节点子树的左边界 lft 和离开该节点子树的右边界 rght。

通过观察树结构的编号分布,可以发现:如果一个节点 A 的左右值范围完全包含了节点 B 的左右值范围(即 A.lft < B.lft AND A.rght > B.rght),那么 B 就是 A 的后代。这种基于数值的区间包含关系,使得我们无需执行递归即可进行高效的层级查询。

建表与初始化准备

为了演示操作,首先建立一张包含层级关系的评论表 comment,并在左值和右值上建立索引以提升查询速度。

DROP TABLE IF EXISTS comment;
CREATE TABLE `comment` (
 `comment_id` int(11) DEFAULT NULL,
 `left_num` int(11) DEFAULT NULL,
 `right_num` int(11) DEFAULT NULL
);

INSERT INTO `comment` VALUES 
 (1,1,14),
 (2,2,5),
 (3,3,4),
 (4,6,13),
 (5,7,8),
 (6,9,12),
 (7,10,11);
 
-- 为左右值建立联合索引
CREATE INDEX idx$comment$left_num$right_num ON `comment` (`left_num`, `right_num`);

第 1 步:查询所有后代子节点

目的:获取指定节点(如节点 4)下的所有子孙节点。

逻辑:只要被查询节点的左右值范围“包住”了目标节点,即为后代。

SELECT c.*
FROM comment AS p, comment AS c
WHERE c.left_num BETWEEN p.left_num AND p.right_num
 AND p.comment_id = 4;

第 2 步:查询所有祖先父节点

目的:获取指定节点(如节点 6)的所有上级节点,用于展示面包屑导航。

逻辑:找出那些其左右值范围“包住”了当前查询节点 c 的其他节点 p。

SELECT p.* 
FROM comment AS p, comment AS c
WHERE c.left_num BETWEEN p.left_num AND p.right_num
 AND c.comment_id = 6;

第 3 步:计算指定节点的深度

注意:在 MySQL 5.7 及以上版本中,可能需要调整 sql_mode 以允许某些聚合或比较行为。

SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';

-- 计算节点 4 的深度,根节点深度为 1
SELECT c.*,
 COUNT(c.comment_id) AS depth
FROM comment AS p, comment AS c
WHERE c.left_num BETWEEN p.left_num AND p.right_num
 AND c.comment_id = 4
GROUP BY c.comment_id;

第 4 步:获取子节点及其对应深度

目的:不仅获取子节点列表,还能显示它们在层级树中的相对深度。通常用于渲染缩进的菜单。

SELECT sub_child.*,
 (COUNT(sub_parent.comment_id) - 1) AS depth
FROM (
 SELECT child.*
 FROM comment AS parent, comment AS child
 WHERE child.left_num BETWEEN parent.left_num AND parent.right_num
  AND parent.comment_id = 4
) AS sub_child, (
 SELECT child.*
 FROM comment AS parent, comment AS child
 WHERE child.left_num BETWEEN parent.left_num AND parent.right_num
  AND parent.comment_id = 4
) AS sub_parent
WHERE sub_child.left_num BETWEEN sub_parent.left_num AND sub_parent.right_num
GROUP BY sub_child.comment_id
ORDER BY sub_child.left_num;

第 5 步:向树结构中插入新节点

插入新节点是嵌套集合中最复杂且容易出错的操作。如果要在某节点(如节点 4)下添加一个新子节点(如节点 44,位置在其现有子节点左侧),需要遵循以下真实操作顺序,切勿直接 INSERT,否则会导致结构错乱。

5.1 准备阶段:更新现有点的值

由于要插入新节点占用空间,必须将目标节点右侧的所有现存节点的左右值全部加 2,腾出空位。

图中紫色部分表示需要变更的节点,绿色为新增节点。
更新思路:
1、将 left_num > 4.lft 的所有节点 left_num 加 2。
2、将 right_num > 4.lft 的所有节点 right_num 加 2。

-- 确认插入位置的基准点(此处假设插入到 left_num=6 之后)
UPDATE comment SET left_num = left_num + 2 WHERE left_num > 6;
UPDATE comment SET right_num = right_num + 2 WHERE right_num > 6;

5.2 执行阶段:插入新节点记录

为新节点赋予连续的左右值,使其刚好位于腾出的空隙中。

INSERT INTO comment 
SELECT 44, left_num + 1, left_num + 2
FROM comment WHERE comment_id = 4;

5.3 验证阶段

插入完成后,重新执行子节点查询,可以观察到节点 44 已正确归属于节点 4 下,且其他兄弟节点位置自动后移。

总结与适用场景

嵌套集合模型极其适合读取频繁、写入较少的场景,例如系统的地区分类、组织架构或永久性的栏目表。如果应用中存在大量的实时层级变更(如频繁的拖拽排序),嵌套集合的更新成本过高。在此类情况下,建议结合物化路径(Materialized Path)或使用消息队列(如 Kafka)异步处理层级同步,以平衡性能。

以上就是 MySQL 嵌套集合模型实现高效树形结构查询与操作的详细内容,更多关于数据库设计优化的资料请关注本站其它相关文章!
相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。