MySQL WITH RECURSIVE 详解

目录

一、什么是 WITH RECURSIVE

WITH RECURSIVE 是 MySQL 提供的一种递归公共表表达式(Recursive Common Table Expression,Recursive CTE)

简单理解:

它允许 SQL 自己调用自己,实现递归查询。

类似于 Java 中:

java 复制代码
public void test(Node node){
    test(node.getChild());
}

SQL 以前无法直接递归,只能:

  • 多次自关联(JOIN)
  • 应用层循环查询
  • 存储过程递归

WITH RECURSIVE 出现后,可以直接在 SQL 中完成树形结构遍历。


二、MySQL 从哪个版本开始支持?

MySQL 从:

MySQL 8.0

开始正式支持:

  • CTE(公共表表达式)
  • Recursive CTE(递归公共表表达式)

即:

sql 复制代码
WITH ...

sql 复制代码
WITH RECURSIVE ...

都是 MySQL 8.0 新增的特性。

MySQL 5.7 及以前:

❌ 不支持


三、它解决了什么问题

开发中经常出现树形结构:

组织架构

text 复制代码
董事长
 ├── 总经理
 │    ├── 技术部
 │    └── 财务部
 └── 人事部

菜单系统

text 复制代码
系统管理
 ├── 用户管理
 ├── 角色管理
 └── 权限管理

行政区划

text 复制代码
中国
 ├── 北京
 ├── 上海
 └── 广东
      ├── 深圳
      └── 广州

评论回复

text 复制代码
评论1
 ├── 回复1
 │    └── 回复2
 └── 回复3

以前查询:

查询所有子节点

需要:

sql 复制代码
select * from dept where parent_id=1;

select * from dept where parent_id in(...);

select * from dept where parent_id in(...);

不断循环。


现在:

sql 复制代码
WITH RECURSIVE

一条 SQL 搞定。


四、WITH RECURSIVE 语法结构

标准语法:

sql 复制代码
WITH RECURSIVE cte_name AS (
    
    -- 初始查询(锚点查询)
    
    SELECT ...

    UNION ALL

    -- 递归查询
    
    SELECT ...
    FROM table t
    JOIN cte_name c
      ON ...
)

SELECT * FROM cte_name;

五、递归执行过程

例如:

sql 复制代码
WITH RECURSIVE nums AS (

    SELECT 1 AS n

    UNION ALL

    SELECT n + 1
    FROM nums
    WHERE n < 5

)

SELECT * FROM nums;

执行步骤:

第一步

执行锚点查询

sql 复制代码
SELECT 1

结果:

text 复制代码
1

第二步

带入递归部分

sql 复制代码
1 + 1

得到:

text 复制代码
2

第三步

继续递归

text 复制代码
3
4
5

结果:

text 复制代码
1
2
3
4
5

六、WITH RECURSIVE 的组成部分

必须包含两部分:

1. Anchor(锚点)

递归起点

sql 复制代码
SELECT 1

2. Recursive(递归部分)

不断调用自身

sql 复制代码
SELECT n+1
FROM nums
WHERE n<5

3. UNION ALL

连接两部分

sql 复制代码
Anchor

UNION ALL

Recursive

七、第一个实战:生成数字序列

生成1~10

sql 复制代码
WITH RECURSIVE nums AS (

    SELECT 1 AS num

    UNION ALL

    SELECT num + 1
    FROM nums
    WHERE num < 10

)

SELECT * FROM nums;

结果:

text 复制代码
1
2
3
4
5
6
7
8
9
10

八、生成日期序列

生成最近7天

sql 复制代码
WITH RECURSIVE dates AS (

    SELECT CURDATE() AS dt

    UNION ALL

    SELECT DATE_SUB(dt,INTERVAL 1 DAY)
    FROM dates
    WHERE dt > CURDATE()-INTERVAL 6 DAY

)

SELECT * FROM dates;

结果:

text 复制代码
2026-06-15
2026-06-14
2026-06-13
...

九、树形结构实战

部门表

sql 复制代码
CREATE TABLE dept(
    id INT PRIMARY KEY,
    dept_name VARCHAR(50),
    parent_id INT
);

数据:

text 复制代码
1 总公司 NULL

2 技术中心 1
3 财务中心 1

4 开发部 2
5 测试部 2

6 Java组 4
7 前端组 4

数据结构

text 复制代码
总公司(1)
├── 技术中心(2)
│    ├── 开发部(4)
│    │    ├── Java组(6)
│    │    └── 前端组(7)
│    └── 测试部(5)
└── 财务中心(3)

十、递归向下查询(查询所有子节点)

查询部门1下面所有节点

sql 复制代码
WITH RECURSIVE dept_tree AS (

    SELECT
        id,
        dept_name,
        parent_id,
        1 level
    FROM dept
    WHERE id = 1

    UNION ALL

    SELECT
        d.id,
        d.dept_name,
        d.parent_id,
        dt.level + 1
    FROM dept d
    JOIN dept_tree dt
      ON d.parent_id = dt.id

)

SELECT *
FROM dept_tree;

结果:

text 复制代码
1 总公司

2 技术中心
3 财务中心

4 开发部
5 测试部

6 Java组
7 前端组

十一、增加层级显示

sql 复制代码
WITH RECURSIVE dept_tree AS (

    SELECT
        id,
        dept_name,
        parent_id,
        1 level
    FROM dept
    WHERE id=1

    UNION ALL

    SELECT
        d.id,
        d.dept_name,
        d.parent_id,
        dt.level+1
    FROM dept d
    JOIN dept_tree dt
      ON d.parent_id=dt.id
)

SELECT
    id,
    dept_name,
    level
FROM dept_tree;

结果:

text 复制代码
id  dept_name   level

1   总公司      1
2   技术中心    2
3   财务中心    2
4   开发部      3
5   测试部      3
6   Java组      4
7   前端组      4

十二、生成完整路径

很多权限系统都这样做。

例如:

text 复制代码
总公司/技术中心/开发部/Java组

SQL:

sql 复制代码
WITH RECURSIVE dept_tree AS (

    SELECT
        id,
        dept_name,
        parent_id,
        dept_name AS path
    FROM dept
    WHERE id=1

    UNION ALL

    SELECT
        d.id,
        d.dept_name,
        d.parent_id,
        CONCAT(dt.path,'/',d.dept_name)
    FROM dept d
    JOIN dept_tree dt
      ON d.parent_id=dt.id
)

SELECT *
FROM dept_tree;

结果:

text 复制代码
总公司

总公司/技术中心

总公司/技术中心/开发部

总公司/技术中心/开发部/Java组

十三、WITH RECURSIVE 能向上查吗?

答案:

完全可以。

很多人误以为只能向下查。

实际上:

递归方向由 JOIN 条件决定。


十四、向上递归查询祖先节点

例如:

查询 Java组(id=6) 的所有上级。


树:

text 复制代码
总公司(1)
└── 技术中心(2)
     └── 开发部(4)
          └── Java组(6)

SQL:

sql 复制代码
WITH RECURSIVE parent_tree AS (

    SELECT
        id,
        dept_name,
        parent_id
    FROM dept
    WHERE id = 6

    UNION ALL

    SELECT
        d.id,
        d.dept_name,
        d.parent_id
    FROM dept d
    JOIN parent_tree pt
      ON d.id = pt.parent_id

)

SELECT *
FROM parent_tree;

结果:

text 复制代码
6 Java组

4 开发部

2 技术中心

1 总公司

十五、向上生成完整路径

sql 复制代码
WITH RECURSIVE parent_tree AS (

    SELECT
        id,
        dept_name,
        parent_id,
        dept_name AS path
    FROM dept
    WHERE id=6

    UNION ALL

    SELECT
        d.id,
        d.dept_name,
        d.parent_id,
        CONCAT(d.dept_name,'/',pt.path)
    FROM dept d
    JOIN parent_tree pt
      ON d.id=pt.parent_id

)

SELECT *
FROM parent_tree
ORDER BY id;

最终得到:

text 复制代码
总公司/技术中心/开发部/Java组

十六、避免死循环

假设数据错误:

text 复制代码
1 -> 2
2 -> 3
3 -> 1

形成环:

text 复制代码
1
↓
2
↓
3
↑
└───

递归将无限执行。


解决方法:

记录访问路径。

sql 复制代码
WITH RECURSIVE dept_tree AS (

    SELECT
        id,
        parent_id,
        CAST(id AS CHAR(1000)) path
    FROM dept
    WHERE id=1

    UNION ALL

    SELECT
        d.id,
        d.parent_id,
        CONCAT(dt.path,',',d.id)
    FROM dept d
    JOIN dept_tree dt
      ON d.parent_id=dt.id
    WHERE FIND_IN_SET(d.id,dt.path)=0

)

SELECT *
FROM dept_tree;

十七、递归层数限制

查看:

sql 复制代码
SHOW VARIABLES LIKE '%recursion%';

通常:

text 复制代码
cte_max_recursion_depth = 1000

表示最多递归1000层。


修改:

sql 复制代码
SET SESSION cte_max_recursion_depth = 5000;

或者:

sql 复制代码
SET GLOBAL cte_max_recursion_depth = 5000;

十八、WITH RECURSIVE 使用规则总结

必须:

sql 复制代码
WITH RECURSIVE name AS(
    anchor

    UNION ALL

    recursive
)

递归部分必须引用自己

sql 复制代码
FROM name

必须有终止条件

sql 复制代码
WHERE level < 100

否则死循环。


推荐使用:

sql 复制代码
UNION ALL

而不是:

sql 复制代码
UNION

因为:

sql 复制代码
UNION

需要去重。

性能更差。


十九、企业开发中的典型应用

组织架构树

text 复制代码
总公司
 └── 分公司
      └── 部门

查询所有下级。


RBAC权限菜单

text 复制代码
系统管理
 ├── 用户管理
 ├── 角色管理
 └── 权限管理

加载菜单树。


评论回复

text 复制代码
评论
 └── 回复
      └── 回复

查询完整评论链。


行政区域

text 复制代码
中国
 └── 广东
      └── 深圳

查询省市区。


商品分类

text 复制代码
电子产品
 └── 手机
      └── 安卓手机

查询所有分类。


二十、面试高频问题

Q1:WITH RECURSIVE 从哪个版本开始支持?

MySQL 8.0。


Q2:WITH 和 WITH RECURSIVE 区别?

复制代码
WITH

普通CTE,不递归。

sql 复制代码
WITH t AS(
  SELECT *
  FROM user
)
SELECT * FROM t;

复制代码
WITH RECURSIVE

支持递归调用自身。


Q3:能否查询父节点?

可以。

改变 JOIN 方向即可。

向下:

sql 复制代码
d.parent_id = tree.id

向上:

sql 复制代码
d.id = tree.parent_id

Q4:为什么推荐 WITH RECURSIVE?

相比循环查询:

  • SQL更简洁
  • 只访问一次数据库
  • 性能更好
  • 天然支持树形结构

总结

WITH RECURSIVE 是 MySQL 8.0 引入的递归查询能力,通过"锚点查询 + UNION ALL + 递归查询"的方式,可以优雅地处理组织架构、菜单树、评论树、行政区划、商品分类等层级数据,既能向下查询所有子孙节点,也能向上查询所有祖先节点,是现代 MySQL 树形数据查询的首选方案。


参考:

mysql递归查询语法WITH RECURSIVE

MySQL RECURSIVE Clauses

MySQL | Recursive CTE (Common Table Expressions)

相关推荐
Upsy-Daisy1 小时前
Hermes Agent 学习笔记 07:Messaging Gateway,让 Agent 从终端走向多平台入口
运维·服务器·数据库
程序员晨曦1 小时前
数据库写轮眼:看透 MVCC 版本链、快照、隔离级别。
数据库·oracle
Leon-Ning Liu1 小时前
MySQL数据恢复实践:binlog2sql数据追加
数据库·mysql
嵌入式-老费1 小时前
esp32开发与应用(看门狗测试)
java·开发语言·数据库
czhc11400756631 小时前
615:代码细节
数据库
炘爚1 小时前
Linux——MySQL
linux·mysql
知识分享小能手1 小时前
Hadoop学习教程,从入门到精通, HBase 分布式数据库 — 完整知识点与案例代码(8)
数据库·hadoop·分布式
吴声子夜歌1 小时前
SQL经典实例——处理数字
java·数据库·sql
NineData1 小时前
日常巡检 Oracle 时,ChatDBA 怎么把会话、SQL 和等待事件一起看
数据库·sql·oracle·ninedata·故障排查·chatdba·实例巡检