CREATE GLOBAL TEMPORARY TABLE 必须显式指定 ON COMMIT 子句,否则默认 ON COMMIT DELETE ROWS 易致 COMMIT 后查不到数据;应根据场景二选一:DELETE ROWS 用于单事务分步处理,PRESERVE ROWS 用于跨事务会话级状态;TRUNCATE 比 DROP+CREATE 更安全高效;结构永久存在且会话间数据隔离,不生成 redo 日志;性能瓶颈多源于排序/哈希操作而非 GTT 本身。

CREATE GLOBAL TEMPORARY TABLE 必须显式指定 ON COMMIT 子句
不写 ON COMMIT 时,Oracle 默认按 ON COMMIT DELETE ROWS 处理,但这个隐式行为极易引发“数据查不到”的困惑——比如在存储过程中插入后没立刻查,等 COMMIT 之后再 SELECT,结果为空。
实际使用中必须二选一:
-
ON COMMIT DELETE ROWS:适合单事务内分步处理,如校验 → 转换 → 提交前汇总,COMMIT或ROLLBACK后自动清空 -
ON COMMIT PRESERVE ROWS:适合跨事务的会话级中间状态,比如报表生成中多步查询共用一张临时表,直到用户登出才释放
错误示例:CREATE GLOBAL TEMPORARY TABLE tmp_log (msg VARCHAR2(200)) —— 语法合法,但语义模糊,上线后容易因默认清空逻辑导致业务中断。
TRUNCATE 比 DROP + CREATE 更安全高效
在 PL/SQL 存储过程中,别用 DROP TABLE + CREATE GLOBAL TEMPORARY TABLE 来“重置”临时表。这不仅慢,还可能触发 ORA-14452(尝试修改正在被引用的临时表),尤其在并发调用时。
正确做法是建好一次后,后续只用:
-
TRUNCATE TABLE tmp_tab:对当前会话数据清空,ON COMMIT PRESERVE ROWS表也有效 - 若需重填且数据量大,用
INSERT /*+ APPEND */ INTO tmp_tab SELECT ...,避免常规INSERT的 buffer cache 开销 - 判断表是否存在?没必要。直接
TRUNCATE,捕获ORA-00942后再建表,比先查user_tables再分支更简洁可靠
临时表数据只对当前会话可见,但结构永久存在
全局临时表的结构注册进数据字典后就一直存在,所有会话都能直接 INSERT/SELECT,无需重复 CREATE。这点和 MySQL 或 SQL Server 的本地临时表(#temp)完全不同。
关键特性包括:
- 每个会话看到的只是自己的数据,即使多个会话同时向同一张
tmp_players插入,彼此不可见 - 不产生数据 redo 日志(但会生成 undo redo),适合高频中间计算
- 索引可建,但仅在表为空时允许;视图、触发器也可挂载,但注意触发器执行上下文仍是当前会话
- 数据物理存于各用户的默认临时表空间(
DBA_USERS.TEMPORARY_TABLESPACE),所以DBA_TABLES.TABLESPACE_NAME显示为空
临时表不是“内存表”,真实压力源在排序/哈希操作
很多人误以为 GTT 数据占满 TEMP 表空间就是它在“吃资源”,其实不然。真正压垮临时表空间的通常是大排序(ORDER BY)、哈希连接(HASH JOIN)、物化子查询等操作,而非 GTT 本身。
验证方式:
- 查当前会话临时段使用:
SELECT username, contents, segtype FROM v$sort_usage - 确认是否真由 GTT 引起:对比
segtype = 'DATA'(GTT 数据段)与'SORT'/'HASH'的占比 - 性能优化重点应放在 SQL 执行计划上,而非盲目扩容 TEMP 表空间
真正容易被忽略的是:GTT 的生命周期语义一旦设错(比如该用 PRESERVE ROWS 却用了默认 DELETE ROWS),问题往往表现为偶发性、难以复现的数据丢失,排查成本远高于建表时多敲几个字。


















