全局临时表(GTT)是“减负开关”而非“加速器”,通过消除DELETE/Redo/Undo开销缓解性能压力;其ON COMMIT DELETE ROWS适用于事务内中间数据,PRESERVE ROWS适用于会话级复用;需避免统计信息共享、TEMP空间不足及数据残留问题。
全局临时表(gtt)在存储过程中不是“加速器”,而是“减负开关”——它不提升单次计算速度,但能直接砍掉 delete/redo/undo 三座大山。
为什么存储过程里用普通中间表会拖垮性能
常见现象是:存储过程执行变慢、AWR 报告里 Redo Generated/sec 突增、UNDO tablespace 告警频发。根本原因在于开发习惯性复用一张普通表(如 inter_data_table)做“插入 → 处理 → DELETE FROM inter_data_table”,而 Oracle 的 DELETE 操作会为每一行生成 Undo 记录和 Redo 日志,高频调用下 I/O 和锁开销指数级放大。
- 事务级中间数据,却用永久表承载 → 日志爆炸
- 多会话并发写同一张中间表 → 需加锁或序列化,丧失并行性
- 每次执行都需
TRUNCATE或DELETE→ 执行计划反复硬解析、统计信息失效
ON COMMIT DELETE ROWS vs ON COMMIT PRESERVE ROWS 怎么选
关键看中间数据的“存活时间窗口”。不是凭感觉,而是按业务逻辑节奏来定:
- 适合
ON COMMIT DELETE ROWS:中间结果只服务于当前事务内的一组操作(例如:校验一批订单 → 标记异常 → 提交后落地)。事务一提交,数据自动清空,无需任何清理语句。 - 适合
ON COMMIT PRESERVE ROWS:中间结果需跨多个事务复用(例如:会话级缓存用户权限树、预加载配置参数)。只要会话没断,数据一直可用,下次调用存储过程时仍可读取。 - 错误用法:
ON COMMIT PRESERVE ROWS表在存储过程中被反复INSERT却不TRUNCATE→ 数据越积越多,查询变慢,且不同调用间可能意外读到旧数据。
创建和使用 GTT 的实操要点
别直接 CREATE GLOBAL TEMPORARY TABLE ... AS SELECT —— 这会触发一次全表扫描并固化结构,还可能因源表统计信息污染 GTT 的会话级统计。正确姿势是分两步:
- 建表时只定义结构,不带数据:
CREATE GLOBAL TEMPORARY TABLE tmp_order_calc (order_id NUMBER, amount NUMBER) ON COMMIT DELETE ROWS; - 在存储过程中显式
INSERT /*+ APPEND */ INTO tmp_order_calc ...,避免归档日志压力(APPEND在 GTT 上有效且安全) - 如果后续要
JOIN大表,给常用过滤字段加索引:CREATE INDEX idx_tmp_order_amt ON tmp_order_calc(amount);(注意:索引也是会话级私有的) - Oracle 12c+ 默认启用会话级统计信息,但首次
INSERT后建议手动收集:DBMS_STATS.GATHER_TABLE_STATS(ownname=>USER, tabname=>'TMP_ORDER_CALC');,否则优化器可能低估行数导致 Nested Loop 效率低下
容易被忽略的三个坑
最麻烦的问题往往不出现在 DDL 或 DML,而出现在上下文环境里:
-
GLOBAL_TEMP_TABLE_STATS参数被设为SHARED:从 11g 升级上来的系统若未重置该参数,所有会话共用一套统计信息,会导致某些会话因数据量小却走 Hash Join,另一些因数据量大却走 Nested Loop。 - 临时表空间不足:GTT 数据虽不写 Redo,但仍依赖
TEMP表空间存放排序/哈希中间结果。监控v$sort_usage,发现segtype = 'TEMPORARY TABLE'占比高时,得扩容tempfile或调优 PGA。 - 误把 GTT 当“高速缓存”长期持有:比如在包变量里反复引用同一 GTT 名,却不注意会话生命周期 —— 连接池复用会话时,前一次残留数据可能干扰下一次逻辑,必须在入口处加
DELETE FROM tmp_xxx;或依赖ON COMMIT策略兜底。


















