在MySQL8.0版本之前,要实现递归查询层级数据,比如查询某个部门下的所有子部门、某个分类下的所有子分类,往往需要编写复杂的存储过程或者多层自连接语句,实现成本高且可读性差。MySQL8.0引入的WITH RECURSIVE公用表表达式语法,让递归查询的实现变得简单直观,只需要按照固定语法结构编写语句就能完成层级数据的遍历查询。

WITH RECURSIVE基本语法结构
WITH RECURSIVE的完整语法由两部分组成,分别是递归锚点部分和递归部分,两部分通过UNION ALL或者UNION连接,基本结构如下:
WITH RECURSIVE 公用表名称 (列1, 列2, ...) AS (
-- 锚点部分:查询初始层级数据,也就是递归的起点
SELECT 列1, 列2, ...
FROM 表名
WHERE 初始条件
UNION ALL
-- 递归部分:基于上一次查询的结果,继续查询下一层级数据
SELECT 表别名.列1, 表别名.列2, ...
FROM 公用表名称 递归别名
INNER JOIN 表名 表别名 ON 递归别名.关联列 = 表别名.父级关联列
)
SELECT * FROM 公用表名称;
其中锚点部分只会执行一次,用来获取递归的初始数据集,递归部分会反复执行,直到没有新的数据返回为止,整个递归过程才会结束。
实际场景示例:查询部门所有下属部门
假设我们有一个部门表dept,表结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | INT | 部门ID,主键 |
| dept_name | VARCHAR(50) | 部门名称 |
| parent_id | INT | 父部门ID,顶级部门该值为NULL |
现在需要查询id为1的部门下的所有子部门,包括间接下属部门,使用WITH RECURSIVE的实现代码如下:
-- 创建测试表并插入测试数据
CREATE TABLE dept (
id INT PRIMARY KEY,
dept_name VARCHAR(50),
parent_id INT
);
INSERT INTO dept VALUES
(1, '总公司', NULL),
(2, '技术部', 1),
(3, '产品部', 1),
(4, '前端组', 2),
(5, '后端组', 2),
(6, '设计组', 3);
-- 递归查询id为1的部门的所有下属部门
WITH RECURSIVE sub_dept AS (
-- 锚点部分:查询初始部门,也就是id为1的部门
SELECT id, dept_name, parent_id
FROM dept
WHERE id = 1
UNION ALL
-- 递归部分:关联查询子部门
SELECT d.id, d.dept_name, d.parent_id
FROM sub_dept sd
INNER JOIN dept d ON sd.id = d.parent_id
)
SELECT * FROM sub_dept;
执行上述查询后,会返回id为1的总公司,以及其下属的技术部、产品部,还有技术部下属的前端组、后端组,产品部下属的设计组,所有层级的部门都会被查询出来。
递归查询的注意事项
- 递归部分必须使用
UNION ALL,如果使用UNION会自动去重,会增加额外的性能开销,除非明确需要去重,否则优先使用UNION ALL。 - 递归查询必须有终止条件,也就是递归部分最终查询不到新的数据,否则会出现无限递归的错误,MySQL默认会限制递归深度,也可以通过
cte_max_recursion_depth参数调整最大递归深度。 - 锚点部分和递归部分查询的列数量和数据类型必须完全一致,否则语句会执行失败。
- 递归公用表表达式不能在递归部分引用自身多次,也不能在递归部分使用
GROUP BY、HAVING、ORDER BY等聚合相关的子句,除非是在子查询中使用。
性能优化建议
如果递归查询的数据量比较大,建议在关联字段上建立索引,比如上述示例中的parent_id字段,建立索引可以加快递归过程中关联查询的速度。另外尽量避免递归过深的查询,如果层级过多可以考虑调整数据结构,比如增加冗余的层级字段来避免递归查询。
需要注意的是,WITH RECURSIVE是MySQL8.0及以上版本才支持的语法,如果是低于8.0的版本,无法使用该语法实现递归查询,只能通过其他方式替代。
MySQL递归查询WITH_RECURSIVE公用表表达式修改时间:2026-07-04 17:21:27