递归CTE查询优化方案(1万ID子资源查询)
问题描述
当 ID_LIST 有 1 万个时,递归 CTE 查询超时。已知数据 2-4 层,父子结构,root 是根节点,parent 是父节点。需求是查找这些资源的子资源 ID,1 万资源大多数是同级节点。
根本原因
- 1万个 IN 参数:大 IN 列表导致查询计划退化,参数绑定本身就很慢
- OR 条件阻断索引 :
r2.root_uuid = r0.uuid OR r2.parent_uuid = r0.uuid无法走复合索引 - 锚点冗余递归: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--- 主键