mysql查询树形,id与pid关联实践

在MySQL中查询基于id和pid关联的树形结构数据,主要有以下几种常用方案,可根据MySQL版本及业务需求选择:
1. 使用递归公用表表达式(推荐 MySQL 8.0+)
这是最标准且性能较好的方式,利用 WITH RECURSIVE语法实现递归查询。
查询指定节点的所有子级(向下查询):
WITH RECURSIVE tree_cte AS (
-- 锚点成员:指定起始节点
SELECT id, pid, name, 1 as level
FROM tree
WHERE id = 1 -- 替换为指定的父节点ID
UNION ALL
-- 递归成员:查找子节点
SELECT t.id, t.pid, t.name, tc.level + 1
FROM tree t
INNER JOIN tree_cte tc ON t.pid = tc.id
)
SELECT * FROM tree_cte;查询指定节点的所有父级(向上查询):
WITH RECURSIVE parent_cte AS (
-- 锚点成员:指定起始节点
SELECT id, pid, name
FROM tree
WHERE id = 5 -- 替换为指定的子节点ID
UNION ALL
-- 递归成员:查找父节点
SELECT t.id, t.pid, t.name
FROM tree t
INNER JOIN parent_cte pc ON t.id = pc.pid
)
SELECT * FROM parent_cte;2. 使用自定义函数(适用于 MySQL 5.7及以下)
在旧版本MySQL中,通常通过创建存储函数,利用循环和 FIND_IN_SET 或 GROUP_CONCAT 来收集所有子节点或父节点的ID字符串,然后再进行查询。
获取所有子节点ID的函数示例逻辑:
- 初始化一个包含根节点ID的字符串变量。
- 循环查询当前层级所有节点的子节点ID。
- 将新发现的子节点ID拼接到字符串中。
- 直到没有新的子节点产生为止。
- 返回ID字符串,外层使用 FIND_IN_SET(id, get_child_ids(root_id)) 进行过滤。
注意:这种方法在数据量较大时性能较差,且受限于 group_concat_max_len 系统变量。
3. 应用层组装(通用方案)
如果数据库查询压力较大或逻辑复杂,可以采用“一次加载,内存组装”的策略:
3.1 SQL查询:一次性查询出所有相关节点(或全表),按 pid 或 level 排序。
SELECT * FROM tree ORDER BY pid, id;
3.2 代码处理:在Java/Python/Go等后端语言中,遍历列表,利用Map将节点挂载到对应的父节点下。
- 创建一个 Map<ID, Node>。
- 遍历所有节点,将节点放入Map。
- 再次遍历,根据 pid 从Map中找到父节点,将当前节点加入父节点的 children 列表。
- 筛选出根节点(pid为0或null)作为树的入口。
4. 其他高级设计模式(针对高性能需求)
如果树形结构非常深且查询频繁,可以考虑改变表结构设计:
- 路径枚举(Path Enumeration):增加 path 字段(如 0,1,5,10),查询子节点时使用 LIKE '0,1,%'。
- 闭包表(Closure Table):新建一张关系表,存储所有祖先-后代关系对,查询时直接Join该表,效率极高但维护成本稍高。
- 嵌套集(MPTT):增加 lft 和 rgt 字段,通过范围查询获取子树,读取性能极佳,但插入和移动节点时需要更新大量数据。
总结建议
如果是 MySQL 8.0+,优先使用 方案1(WITH RECURSIVE),语法简洁且由数据库引擎优化。
如果是 MySQL 5.7 且数据量小,可使用 方案2(自定义函数)。
如果数据量大且业务逻辑复杂,推荐 方案3(应用层组装),减少数据库递归压力。
如果对读取性能有极致要求且写操作较少,考虑 方案4(闭包表或路径枚举)。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
相关文章
Mysql 5.6.37 winx64安装双版本mysql笔记记录
机器上现在已经存在5.0版本MySQL的情况下装一个最新版的mysql,下文通过实例代码给大家介绍Mysql 5.6.37 winx64安装双版本mysql笔记记录,感兴趣的朋友一起看看吧2017-07-07


最新评论