mysql 8.0递归cte本身不持长锁,但事务包裹下每轮迭代均独立执行select并受隔离级别约束,若某轮因行锁阻塞则整个事务挂起;max_recursion_depth仅防无限递归而非控锁,需配合索引、skip locked及合理终止条件优化。

递归CTE本身不持锁,但事务边界会放大阻塞风险
MySQL 8.0 的 WITH RECURSIVE 查询不会主动“持有长锁”,但它每一轮迭代都是一次独立的 SELECT 执行,会按需访问数据行。如果整个递归查询被包裹在显式事务中(比如 BEGIN → 递归查询 → COMMIT),那么所有轮次产生的行扫描、JOIN 和条件判断,都会受该事务的隔离级别约束——意味着:某一轮卡在等锁,整个事务就卡住,锁自然“变长”。
- 常见现象:递归过程中某轮执行
JOIN departments d ON d.parent_id = dt.id,而d表中某行正被另一事务UPDATE ... FOR UPDATE锁住 → 当前轮次阻塞,后续轮次无法启动,事务持续 open - 锚点(initial query)只跑一次,影响小;递归成员可能执行几十甚至上百轮,每轮都可能触发新锁等待
- 没加
SKIP LOCKED时,IN子查询或关联查法容易因某条脏数据反复争抢同一行
max_recursion_depth 不是锁控制开关,只是防雪崩兜底
max_recursion_depth 是会话级变量,默认值 1000,它只限制“最多迭代多少轮”,不干预锁行为。超限后报错 ERROR 3636 (HY000): Recursive query aborted after 1000 iterations,本质是轮次计数器溢出,而非主动释放锁。
- 它防的是无限递归(如 A→B→C→A 循环引用),不是缩短锁时间
- 生产环境建议显式设低:例如
SET SESSION max_recursion_depth = 50,根据业务树深调整 - 该设置仅对当前会话生效,应用层必须在执行递归前
SET,不能依赖全局配置
递归成员必须放在 JOIN 右侧,否则语法直接失败
MySQL 强制要求:递归 CTE 在每轮迭代中只能作为被驱动表(即 JOIN 的右表),不能出现在左侧或 LEFT JOIN 的右端。否则报错 ERROR 3641 (HY000): Recursive reference to CTE 'xxx' is not allowed in this context。
- 正确写法:
FROM departments d INNER JOIN dept_tree dt ON d.parent_id = dt.id - 错误写法:
FROM dept_tree dt INNER JOIN departments d ON d.parent_id = dt.id(CTE 在左) - 错误写法:
FROM departments d LEFT JOIN dept_tree dt ON ...(CTE 在 LEFT JOIN 右侧也不允许) - 原因:优化器模型要求每轮以上一轮 CTE 输出为驱动源,去探查基础表;颠倒顺序会破坏迭代逻辑
终止条件必须写在递归分支 WHERE 中,且要防自循环
递归停止的唯一条件是“某轮递归查询返回空集”,不是靠行数或时间。如果 WHERE 条件写得松或有漏洞,就会导致无效轮次堆积、锁持续累积。
- 典型坑:
WHERE parent_id IS NOT NULL看似合理,但若存在parent_id = id的脏数据,每轮都会匹配自身,永不终止 - 向上查祖先时,锚点应是目标节点(
WHERE id = 123),递归部分 JOIN 条件应为ON d.id = p.parent_id(基础表关联 CTE 的 parent_id),反了就错 - 务必加深度防护:
WHERE p.parent_id IS NOT NULL AND p.level ,避免单次查询拖垮连接池 - 索引缺失会让每轮都全表扫描:
parent_id字段没索引,深树查询直接触发大量隐式锁升级











