递归CTE查询优化方案

递归CTE查询优化方案(1万ID子资源查询)

问题描述

当 ID_LIST 有 1 万个时,递归 CTE 查询超时。已知数据 2-4 层,父子结构,root 是根节点,parent 是父节点。需求是查找这些资源的子资源 ID,1 万资源大多数是同级节点。

根本原因

  1. 1万个 IN 参数:大 IN 列表导致查询计划退化,参数绑定本身就很慢
  2. OR 条件阻断索引r2.root_uuid = r0.uuid OR r2.parent_uuid = r0.uuid 无法走复合索引
  3. 锚点冗余递归:1万个同级节点共享同一批父节点,递归会对相同父节点重复展开

方案一:临时表替换 IN(最直接)

sql 复制代码
-- 先把 1 万 ID 写入临时表
CREATE TEMPORARY TABLE tmp_input_ids (uuid VARCHAR(64) PRIMARY KEY);
INSERT INTO tmp_input_ids VALUES (...批量插入...);

WITH RECURSIVE tree(uuid, parent_uuid) AS (
    SELECT r.uuid, r.parent_uuid
    FROM resources r
    JOIN tmp_input_ids t ON r.uuid = t.uuid   -- JOIN 替代 IN,走索引

    UNION ALL

    SELECT r.uuid, r.parent_uuid
    FROM resources r
    JOIN tree ON r.parent_uuid = tree.uuid    -- 拆掉 OR,只走 parent_uuid
    WHERE r.uuid NOT IN (SELECT uuid FROM tmp_input_ids)
)
SELECT uuid FROM tree;

root_uuid 那个条件如果是为了"一跳找到所有后代"的优化,在深度已知为 2-4 层时可以去掉,让递归自然展开反而更快。


方案二:锚点去重(减少递归起点)

1万同级节点的父节点数量很少,先收缩锚点:

sql 复制代码
WITH RECURSIVE tree AS (
    -- 只保留"在输入列表中、但父节点不在输入列表中"的节点作为锚点
    SELECT r.uuid, r.parent_uuid
    FROM resources r
    JOIN tmp_input_ids t ON r.uuid = t.uuid
    WHERE r.parent_uuid NOT IN (SELECT uuid FROM tmp_input_ids)
      AND r.parent_uuid IS NOT NULL

    UNION ALL

    SELECT r.uuid, r.parent_uuid
    FROM resources r
    JOIN tree ON r.parent_uuid = tree.uuid
)
SELECT uuid FROM tree
WHERE uuid NOT IN (SELECT uuid FROM tmp_input_ids);  -- 排除输入节点本身

这样锚点从 1 万缩减到实际的"根入口"数量(可能只有几十个)。


方案三:固定层数展开(最适合 2-4 层场景)

深度已知时,直接用 JOIN 展开比递归 CTE 快得多:

sql 复制代码
-- 第1层子节点
SELECT uuid FROM resources WHERE parent_uuid IN (SELECT uuid FROM tmp_input_ids)
  AND uuid NOT IN (SELECT uuid FROM tmp_input_ids)

UNION

-- 第2层
SELECT r2.uuid FROM resources r1
JOIN resources r2 ON r2.parent_uuid = r1.uuid
WHERE r1.parent_uuid IN (SELECT uuid FROM tmp_input_ids)
  AND r2.uuid NOT IN (SELECT uuid FROM tmp_input_ids)

UNION

-- 第3层(如有)
SELECT r3.uuid FROM resources r1
JOIN resources r2 ON r2.parent_uuid = r1.uuid
JOIN resources r3 ON r3.parent_uuid = r2.uuid
WHERE r1.parent_uuid IN (SELECT uuid FROM tmp_input_ids)
  AND r3.uuid NOT IN (SELECT uuid FROM tmp_input_ids)

方案四:应用层分批

如果不能用临时表,在 Java/应用层把 1 万 ID 分成 500 一批,并发查询后合并去重。


推荐组合

场景 推荐
可以建临时表 方案一 + 方案二(锚点去重)
深度严格 ≤ 4 层 方案三(固定展开,最可预测)
只能改应用层 方案四分批 + 结果去重

索引要求

  • resources(parent_uuid) --- 必须存在
  • resources(root_uuid) --- 必须存在
  • uuid --- 主键
相关推荐
Mr.Daozhi23 分钟前
RAG 进阶实战:跑通 Demo 后我连续翻了 6 次车,逐一修复才真正可用(含 Gradio Web 版)
前端·数据库·langchain·大模型·gradio·rag·科研工具
小程故事多_8028 分钟前
Claude Code自定义workflow skills用法
数据库·人工智能·智能体
大鹏说大话28 分钟前
SQL 排序与分组实战:解决“分组后取最新数据“
android·java·数据库
夏贰四1 小时前
数据建模工具如何筑牢数据根基?数据建模工具怎样落实标准体系?
数据库·数学建模·数据建模工具
程序猿阿伟2 小时前
《一套完整方法论:搞定图形应用的Docker镜像优化》
数据库·docker·容器
二等饼干~za8986683 小时前
geo优化源码开发搭建技术分享
大数据·网络·数据库·人工智能·音视频
数据库小学妹3 小时前
HTAP混合负载架构:如何用一个数据库同时搞定交易和分析
数据库·经验分享·架构·dba
wuxinyan1233 小时前
工业级大模型学习之路029:解决双智能体调用数据库报错问题
数据库·人工智能·python·学习·智能体
Elastic 中国社区官方博客3 小时前
Elastic 线下 Meetup 将于 2026 年 7 月 26 号下午在深圳举行
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
YL200404263 小时前
【Redis实战篇】秒杀实现方案(以优惠券秒杀为例)
数据库·redis