MySQL CTE必须以WITH开头且紧邻主查询,支持非递归和递归(需WITH RECURSIVE)两种形式,多CTE用逗号分隔并遵循引用顺序,列名建议显式声明,递归CTE须设终止条件并防环路。

WITH 不是配置项,是语法结构——MySQL 8.0.0 及以上版本默认支持,无需开启开关、加载插件或修改配置。
CTE 必须以 WITH 开头,且紧贴主查询
常见错误是把它塞进子查询里,或者写在 SELECT 后面:
-
SELECT * FROM (WITH t AS (...) SELECT ...) s;→ 报错ERROR 1064 -
SELECT id, (WITH t AS (...) SELECT COUNT(*) FROM t) cnt FROM users;→ 不支持标量子查询内用WITH
正确写法:整个语句必须以 WITH 起始,后接主查询(SELECT/UPDATE/DELETE):
WITH recent_orders AS ( SELECT order_id, customer_id, amount FROM orders WHERE order_date >= '2025-01-01' ) SELECT customer_id, SUM(amount) AS total FROM recent_orders GROUP BY customer_id;
多 CTE 要用逗号分隔,引用顺序不能乱
多个 CTE 写在一个 WITH 子句里,用逗号连接;后一个可引用前一个,但反向不行:
-
WITH a AS (), b AS (SELECT * FROM a), c AS (SELECT * FROM b)✅ -
WITH a AS (SELECT * FROM b), b AS ()❌ 报错Unknown table 'b'
列名建议显式声明,尤其含表达式时,避免歧义:
WITH sales_summary (customer_id, total, avg_order) AS ( SELECT customer_id, SUM(amount), AVG(amount) FROM orders GROUP BY customer_id ) SELECT * FROM sales_summary WHERE total > 1000;
递归 CTE 必须带 RECURSIVE 关键字,且 cte_max_recursion_depth 不能为 0
漏写 RECURSIVE 直接报错;更隐蔽的问题是系统变量被设为 0(某些云厂商默认禁用):
- 检查:
SELECT @@cte_max_recursion_depth; - 启用(需
SUPER权限):SET GLOBAL cte_max_recursion_depth = 1000; - 结构必须严格:非递归种子
UNION ALL递归成员,顺序颠倒会报错ERROR 1223 - 递归引用只能出现在
FROM或JOIN中,不能在WHERE右侧子查询里
示例(组织树):
WITH RECURSIVE org_tree AS ( SELECT id, name, parent_id, 0 AS level FROM org WHERE parent_id IS NULL UNION ALL SELECT o.id, o.name, o.parent_id, ot.level + 1 FROM org o INNER JOIN org_tree ot ON o.parent_id = ot.id ) SELECT * FROM org_tree ORDER BY level, id;
CTE 不是万能胶水,滥用反而难 debug
适合场景:重复子查询、多层嵌套、逻辑分段;不适合场景:单次引用、相关子查询、高频小数据量查询:
- 用 CTE 拆开客户订单数 + 最高金额 + 首次下单时间,可复用中间结果、改字段只改一处
- 但若只是查一次平均值:
WITH avg_sal AS (SELECT AVG(salary) FROM emp) SELECT * FROM emp WHERE salary > (SELECT * FROM avg_sal);—— 性能未必优于直接写子查询,且增加一层抽象 - CTE 在 MySQL 中默认不物化(PostgreSQL 默认也不物化),多次引用可能重复执行,性能敏感时得结合执行计划看实际行为
真正容易被忽略的点:递归 CTE 的终止条件靠 WHERE 控制,但 MySQL 不自动检测环路;若数据存在自引用(parent_id = id)或双向父子关系,会跑满 cte_max_recursion_depth 才停,期间 CPU 和内存持续上涨。


















