with recursive 递归查询必须显式设置终止条件(如 depth 限制或路径去重),否则 postgresql/sqlite/mysql 8.0+ 均报错;依赖数据自然结束不可靠,需用 array 类型配合 not … any(path) 防环并避免误匹配。
WITH RECURSIVE 递归查询必须有终止条件,否则直接报错
PostgreSQL 和 SQLite 支持 WITH RECURSIVE,但 MySQL 8.0+ 才支持;不加明确的终止逻辑会触发 ERROR: recursive query terminated due to cycle detection 或更常见的 ERROR: stack depth limit exceeded。这不是性能问题,是 SQL 引擎强制的安全限制。
实际写法里,终止靠的是递归成员中对 depth 或路径字符串长度的显式约束,而不是依赖“数据自然结束”。图里没有环 ≠ 查询不会无限递归——比如起点连向自己、或两个节点互相指向,都可能绕过环检测(尤其用字符串拼接路径时)。
- 必须在递归部分加入
WHERE depth 这类硬限制(根据业务场景调) - 推荐用数组记录已访问节点:
NOT node_id = ANY(path),比字符串NOT LIKE更可靠 - SQLite 对递归深度默认只允许 1000 层,需运行
PRAGMA recursive_triggers = ON并设PRAGMA max_recursive_depth = 2000
构建路径时优先用 ARRAY 而非字符串拼接
用 CONCAT(prev_path, '->', node_id) 看似直观,但会导致无法准确判断是否成环(比如 1->12 会被 '1' 错误匹配),也难做去重和长度统计。PostgreSQL 的 ARRAY 类型天然支持 UNION 去重、@> 包含判断、array_length() 计数。
典型错误是把初始 CTE 的 path 设为 ARRAY[node_id],却在递归部分写成 ARRAY_APPEND(path, next_node) ——这没问题,但若漏掉 DISTINCT ON (node_id),同一节点多次被不同路径抵达时,会重复展开子树,爆炸式增长结果行数。