Oracle 游标持续时间临时表(Cursor Duration Temporary Table)解析

在 Oracle 里跑一条复杂 SQL 时,你有没有遇到过这样的情况: WITH 子查询被多次引用、星形转换里反复扫描事实表、GROUPING SET 不断重算同一批聚合…… CPU、I/O 都飙高,却只是在做重复工作。

Oracle 21c 之后引入的“游标持续时间临时表(Cursor Duration Temporary Table,简称 CDTT)”正是为了解决这个问题。 它像一块“中间结果缓存”,把重复计算的结果暂存在内存(必要时落盘),让后续步骤直接拿来用,从而显著降低资源消耗。


CDTT 要解决什么问题?

场景重复计算示例带来的开销
WITH 子查询多次引用WITH q1 AS (SELECT …) SELECT * FROM q1 UNION ALL SELECT * FROM q1 …q1 被多次执行
星形转换事实表过滤后又被不同维度反复聚合大表扫描多次
GROUPING SET同一批 GROUP BY 被拆成多份执行聚合反复算

CDTT 把“中间结果”实体化成一张临时表,后续步骤直接 SELECT 这张表即可。


工作机制 6 步曲

  1. 计划选择 优化器发现某段子查询会被多次访问,于是生成使用 CDTT 的执行计划。
  2. 内存建表 以会话私有方式,在 PGA 中创建一张“只活于游标生命周期”的临时表(名字形如 SYS_TEMP_0FD9D6606_1AE004)。 元数据 不在数据字典落地,查询 DBA_OBJECTS 看不到。
  3. 查询重写 优化器把后续对子查询的引用全部改写成对这张临时表的扫描。
  4. 数据加载 先向内存中的临时表 LOAD AS SELECT;PGA 不足时自动 spill 到临时段(类似排序 spill)。
  5. 执行并返回 后续步骤直接 TABLE ACCESS FULL 临时表,避免重复计算。
  6. 游标关闭即清理 游标关闭或从 Library Cache 淘汰时,内存/磁盘段一并 TRUNCATE 并释放。

执行计划里长什么样?

只要看到 TEMP TABLE TRANSFORMATIONLOAD AS SELECT (CURSOR DURATION MEMORY) 成对出现,就可以确认 CDTT 已启用:

--------------------------------------------------------------------
| Id | Operation                                 | Name                   |
--------------------------------------------------------------------
| 1 | TEMP TABLE TRANSFORMATION               |                       |
| 2 |   LOAD AS SELECT (CURSOR DURATION MEMORY) | SYS_TEMP_0FD9D6606_... |
| 3 |   HASH GROUP BY                         |                       |
| 4 |     TABLE ACCESS FULL                     | EMPLOYEES             |
| 5 |   UNION-ALL                               |                       |
| 6 |   VIEW                                   |                       |
| 7 |     TABLE ACCESS FULL                     | SYS_TEMP_0FD9D6606_... |
| 8 |   VIEW                                   |                       |
| 9 |     TABLE ACCESS FULL                     | SYS_TEMP_0FD9D6606_... |
--------------------------------------------------------------------
  • SYS_TEMP_0FD9D6606_... 就是 CDTT。
  • CURSOR DURATION MEMORY 关键字说明优先内存,PGA 不足才落盘。

一个极简示例

WITH
q1 AS (SELECT department_id, SUM(salary) sum_sal
          FROM hr.employees
         GROUP BY department_id),
q2 AS (SELECT * FROM q1),
q3 AS (SELECT * FROM q1)
SELECT * FROM q1
UNION ALL
SELECT * FROM q2
UNION ALL
SELECT * FROM q3;

执行计划如上所示,q1 的结果只算一次,被写入 CDTT,后三条 SELECT * FROM q1/q2/q3 全部变成对同一张临时表的全表扫描。


使用注意

  • 只对当前会话可见:不用担心并发冲突。
  • 无法手动干预:不能 DROP TABLE SYS_TEMP_...,也不能加 hint 强制开关。
  • 监控
    • V$SQL_PLAN 可以看到 OBJECT_NAME 字段出现 SYS_TEMP_%
    • V$PGASTAT/V$TEMPSEG_USAGE 可观察 PGA 与临时段的使用。
  • 性能对比:对重复子查询多的报表、ETL 作业,打开 STATISTICS_LEVEL=ALL 跑 AWR 报告,通常能看到 CPU、逻辑读明显下降。

下次看到执行计划里出现 CURSOR DURATION MEMORY,别慌 —— 那是 Oracle 悄悄帮你把“重复劳动”存起来,让复杂 SQL 一次跑完、不再折腾。

把 CDTT 当成“游标级缓存”,用好它,你的长查询也能跑得既快又省。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部