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

简单理解:
它允许 SQL 自己调用自己,实现递归查询。
类似于 Java 中:
public void test(Node node){ test(node.getChild());}SQL 以前无法直接递归,只能:
而 WITH RECURSIVE 出现后,可以直接在 SQL 中完成树形结构遍历。
MySQL 从:
MySQL 8.0
开始正式支持:
即:
WITH ...
和
WITH RECURSIVE ...
都是 MySQL 8.0 新增的特性。
MySQL 5.7 及以前:
❌ 不支持
开发中经常出现树形结构:
董事长 ├── 总经理 │ ├── 技术部 │ └── 财务部 └── 人事部
系统管理 ├── 用户管理 ├── 角色管理 └── 权限管理
中国 ├── 北京 ├── 上海 └── 广东 ├── 深圳 └── 广州
评论1 ├── 回复1 │ └── 回复2 └── 回复3
以前查询:
需要:
select * from dept where parent_id=1;select * from dept where parent_id in(...);select * from dept where parent_id in(...);
不断循环。
现在:
WITH RECURSIVE
一条 SQL 搞定。
标准语法:
WITH RECURSIVE cte_name AS ( -- 初始查询(锚点查询) SELECT ... UNION ALL -- 递归查询 SELECT ... FROM table t JOIN cte_name c ON ...)SELECT * FROM cte_name;
例如:
WITH RECURSIVE nums AS ( SELECT 1 AS n UNION ALL SELECT n + 1 FROM nums WHERE n < 5)SELECT * FROM nums;
执行步骤:
执行锚点查询
SELECT 1
结果:
1
带入递归部分
1 + 1
得到:
2
继续递归
345
结果:
12345
必须包含两部分:
递归起点
SELECT 1
不断调用自身
SELECT n+1FROM numsWHERE n<5
连接两部分
AnchorUNION ALLRecursive
WITH RECURSIVE nums AS ( SELECT 1 AS num UNION ALL SELECT num + 1 FROM nums WHERE num < 10)SELECT * FROM nums;
结果:
12345678910
生成最近7天
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;
结果:
2026-06-152026-06-142026-06-13...
CREATE TABLE dept( id INT PRIMARY KEY, dept_name VARCHAR(50), parent_id INT);
数据:
1 总公司 NULL2 技术中心 13 财务中心 14 开发部 25 测试部 26 Java组 47 前端组 4
总公司(1)├── 技术中心(2)│ ├── 开发部(4)│ │ ├── Java组(6)│ │ └── 前端组(7)│ └── 测试部(5)└── 财务中心(3)
查询部门1下面所有节点
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;
结果:
1 总公司2 技术中心3 财务中心4 开发部5 测试部6 Java组7 前端组
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, levelFROM dept_tree;
结果:
id dept_name level1 总公司 12 技术中心 23 财务中心 24 开发部 35 测试部 36 Java组 47 前端组 4
很多权限系统都这样做。
例如:
总公司/技术中心/开发部/Java组
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;
结果:
总公司总公司/技术中心总公司/技术中心/开发部总公司/技术中心/开发部/Java组
答案:
完全可以。
很多人误以为只能向下查。
实际上:
递归方向由 JOIN 条件决定。
例如:
查询 Java组(id=6) 的所有上级。
树:
总公司(1)└── 技术中心(2) └── 开发部(4) └── Java组(6)
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;
结果:
6 Java组4 开发部2 技术中心1 总公司
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_treeORDER BY id;
最终得到:
总公司/技术中心/开发部/Java组
假设数据错误:
1 -> 22 -> 33 -> 1
形成环:
1↓2↓3↑└───
递归将无限执行。
解决方法:
记录访问路径。
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;
查看:
SHOW VARIABLES LIKE '%recursion%';
通常:
cte_max_recursion_depth = 1000
表示最多递归1000层。
修改:
SET SESSION cte_max_recursion_depth = 5000;
或者:
SET GLOBAL cte_max_recursion_depth = 5000;
必须:
WITH RECURSIVE name AS( anchor UNION ALL recursive)
递归部分必须引用自己
FROM name
必须有终止条件
WHERE level < 100
否则死循环。
推荐使用:
UNION ALL
而不是:
UNION
因为:
UNION
需要去重。
性能更差。
总公司 └── 分公司 └── 部门
查询所有下级。
系统管理 ├── 用户管理 ├── 角色管理 └── 权限管理
加载菜单树。
评论 └── 回复 └── 回复
查询完整评论链。
中国 └── 广东 └── 深圳
查询省市区。
电子产品 └── 手机 └── 安卓手机
查询所有分类。
MySQL 8.0。
WITH
普通CTE,不递归。
WITH t AS( SELECT * FROM user)SELECT * FROM t;
WITH RECURSIVE
支持递归调用自身。
可以。
改变 JOIN 方向即可。
向下:
d.parent_id = tree.id
向上:
d.id = tree.parent_id
相比循环查询:
WITH RECURSIVE 是 MySQL 8.0 引入的递归查询能力,通过“锚点查询 + UNION ALL + 递归查询”的方式,可以优雅地处理组织架构、菜单树、评论树、行政区划、商品分类等层级数据,既能向下查询所有子孙节点,也能向上查询所有祖先节点,是现代 MySQL 树形数据查询的首选方案。
参考:
mysql递归查询语法WITH RECURSIVE
MySQL RECURSIVE Clauses
MySQL | Recursive CTE (Common Table Expressions)