如何通过递归查询获取用户推荐链路信息

来源:运维教程作者:苏锦程头衔:网络博主
导读:本期聚焦于小伙伴创作的《如何通过递归查询获取用户推荐链路信息》,敬请观看详情。你的App里嵌入了推荐奖励机制,某个用户突然问:“是谁推荐的我?他的上级又是谁?”如果用户表里只存了一个上级ID,怎么用一条SQL就查出整条推荐链条?传统JOIN只能固定层级,一旦链条深度不确定,查询就变得极其笨拙。递归查询正是为解决这类“不确定层级”的树形或链式数据而生。本文从实际业务场景出发,解析SQL递归查询的语法规则,演示如何用一条语句向上追溯完整推荐人链路,以及向下展开整棵被推荐人树。同时讨论循环依赖、死循环防护和性能优化策略,帮你稳稳应对推荐系统里的链路追溯需求。

如何通过递归查询获取用户推荐链路信息

在用户推荐系统中,每个用户都可能存在一个“推荐人”,形成一条类似引荐关系的单向链表,或者多个下级形成树状结构。这类数据通常存储在一张用户表中,只记录直接上级的ID,比如字段 inviter_id。当需要查看某个用户的完整推荐链路时,传统做法是通过多次查询或者代码循环逐层向上获取,直到链路的顶端(没有推荐人的用户)。这种方式不仅效率低,而且当链路层级较深时,会产生大量数据库连接或应用层计算。递归查询(Recursive Query)的出现,使得我们可以在单条SQL语句中完成任意层级的数据检索,极大简化了链路查询的实现。

递归查询的语法与执行原理

多数现代关系型数据库都支持递归查询,例如MySQL 8.0以上、PostgreSQL、SQL Server以及Oracle。它们的核心思路都是通过 WITH RECURSIVE 语句定义一个递归公共表表达式(CTE)。递归 CTE 包含两个部分:初始查询(非递归部分)和递归部分,这两部分通过 UNION ALL 连接。

初始查询获取第一层数据,也就是递归的起点。例如,如果我们想向上追溯某个用户的推荐人链路,初始查询就是查出这个用户自己的记录。递归部分则基于上一次迭代的结果,继续向上或向下寻找相关联的数据。数据库会反复执行递归部分,直到某一次返回空结果集时停止。整个过程类似于一个循环,但由数据库引擎内部完成,比自行在应用层递推要高效得多。

以下是一个最基础的递归查询结构示例,假设有一张 users 表,包含 idnicknameinviter_id 字段:

WITH RECURSIVE user_chain AS (
    -- 初始查询:获取目标用户本身
    SELECT id, nickname, inviter_id, 0 AS depth
    FROM users
    WHERE id = 1001
    
    UNION ALL
    
    -- 递归部分:通过 inviter_id 关联到上级用户
    SELECT u.id, u.nickname, u.inviter_id, uc.depth + 1
    FROM users u
    INNER JOIN user_chain uc ON u.id = uc.inviter_id
)
SELECT * FROM user_chain
ORDER BY depth;

上面的例子是向上递归的例子,递归部分通过当前链路的 inviter_id 去找对应的用户,并将层级深度加1。当某条记录的 inviter_idNULL 或者找不到匹配时,递归停止。注意到我们使用了 UNION ALL 而不是 UNION,这是因为递归部分不需要去重,使用 UNION ALL 可以避免不必要的排序操作,提升性能。

向上递归:追溯用户的完整推荐人链路

在业务中,向上追溯最常见的情景就是“查上级”,比如用户想看看是哪个好友推荐了自己注册。假设我们想获取用户 Andy 的所有推荐人,从直接上级一直到最顶层的没有推荐人的用户。利用递归查询,我们只需要将初始查询的起点设置为 Andy 的用户记录,并在递归中一直向上找到 inviter_id 对应的用户即可。

进一步考虑展示需求,我们往往需要将链路结果按从上到下的顺序排列,最顶层排在前面,目标用户排在最后。由于递归查询默认按照深度递增的顺序产出记录,我们可以直接使用深度列排序,或者反转排序。但更直观的做法是设置一个起点深度为0,每次递归深度加1,最终查询时倒序排列,使顶端深度最大,目标用户深度最小。以下示例展示了如何查询用户 id 为 1001 的所有上级链路:

WITH RECURSIVE up_chain AS (
    SELECT id, nickname, inviter_id, 0 AS level
    FROM users
    WHERE id = 1001
    
    UNION ALL
    
    SELECT u.id, u.nickname, u.inviter_id, uc.level + 1
    FROM users u
    INNER JOIN up_chain uc ON u.id = uc.inviter_id
)
SELECT id, nickname, level
FROM up_chain
ORDER BY level DESC;

这种查询完全避免了多次 SELECT 或循环调用,无论链路的长度是3层还是30层,一条SQL都能返回。需要注意的是,如果存在循环依赖(例如 A 的上级是 B,B 的上级又设置成了 A),递归查询会陷入死循环。预防措施可以在递归部分加入路径记录,检测重复的 id,或者设置递归深度上限(例如在 MySQL 中通过系统变量 cte_max_recursion_depth 控制最大递归次数)。

向下递归:展开用户的全部下级推荐关系树

与向上递归相反,向下递归用于查看某个用户直接或间接推荐了哪些人,形成一个树状的推荐网络。此时初始查询应选定某个用户作为树根,递归部分则改为通过父级的 id 去匹配子级记录中的 inviter_id。通过这种方式,可以轻松得到某个推广员的下级人员列表,甚至是多级下线的完整结构。

例如,我们要查询推广员 Mike(id 为 1000)旗下所有被推荐用户,可以这样写:

WITH RECURSIVE down_chain AS (
    SELECT id, nickname, inviter_id, 0 AS depth
    FROM users
    WHERE id = 1000
    
    UNION ALL
    
    SELECT u.id, u.nickname, u.inviter_id, dc.depth + 1
    FROM users u
    INNER JOIN down_chain dc ON u.inviter_id = dc.id
)
SELECT id, nickname, inviter_id, depth
FROM down_chain
ORDER BY depth, id;

上述结果集中包含了 Mike 自己以及所有层级的被推荐人。如果不需要包含根节点,可以在初始查询后加入 WHERE 条件过滤,但通常在树状展示中根节点是有意义的。为了更清晰地展现层级关系,我们还可以在查询中使用 LPADREPEAT 函数生成缩进的前缀,结合 depth 字段在应用层渲染出树状视图。

实际项目中,推荐关系常常需要统计每个用户的邀请人数、邀请带来的收益等。借助于向下递归查询的结果,我们可以将这些聚合操作放在递归结束后进行,比如统计每个上级所拥有的所有下级数量,从而计算佣金。此类查询一般通过将递归结果作为临时表,再与订单表等关联,完成复杂的多维分析。

递归查询的优化与常见陷阱

递归查询虽然强大,但如果不注意细节,很可能导致性能问题甚至死循环。第一个要点是索引设计。递归查询中,递归部分通常会频繁访问连接条件中的字段,例如 u.id = uc.inviter_idu.inviter_id = dc.id。为了加速这些匹配,必须在 users 表的 inviter_id 字段上建立索引,同时 id 通常是主键已被索引,否则大量全表扫描会拖垮数据库。

第二个是循环依赖与深度控制。在数据可能被修改或存在脏数据的情况下,推荐关系容易出现环。除前面提到的路径检测方法外,一种更简单的方式是设置递归深度上限,防止无限循环。MySQL 中可以使用 SET SESSION cte_max_recursion_depth = 100; 来限制最大递归层数,默认值为 1000。生产环境可根据实际业务预期层级进行调整,并配合代码中的异常处理。

第三是数据量控制。向下递归时,如果某个超级推广员的下级数量极其庞大(例如数十万),一次性递归出所有记录可能会导致内存占用过高和查询超时。此时可以考虑分页拉取,或者结合业务场景限制查询深度,比如只查询3级以内的下线。还可以在递归中增加 WHERE 条件提前裁剪不需要的分支,例如只查询某个时间段内注册的下级。

最后,注意不同数据库之间的语法差异。虽然 WITH RECURSIVE 已经成为标准 SQL 的一部分,但在具体函数支持上仍有差别。例如在 PostgreSQL 中可以方便地使用数组存储路径,而 MySQL 8.0 可能需要在递归部分用字符串拼接路径。掌握这些差异,才能在跨数据库环境中游刃有余。

递归查询用户推荐链路树形数据修改时间:2026-08-12 16:06:55

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。