Oracle全局临时表和私有临时表
Posted dingdingfish
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Oracle全局临时表和私有临时表相关的知识,希望对你有一定的参考价值。
Oracle 全局临时表以下简称GTT(Global Temporary Table),私有临时表以下简称PTT(Private Temporary Table)。
GTT和PTT的主要区别如下:
- GTT更早。GTT是8i就有的特性,PTT是18c才有的特性。
- GTT和PTT的数据都是会话私有的,而且会话结束后数据就没有了,但GTT的定义是全局的。
- 事务提交时,GTT可以选择是否保留数据,PTT可以选择是否保留定义。
- GTT的表定义是所有会话共享的;PTT的表定义是会话私有的。
关于GTT和PTT的实验,可以分别参考Oracle Base的这篇和这篇文章。
GTT和PTT建表语法示例可参考这里。
GTT的实验
-- 默认是ON COMMIT DELETE ROWS
SQL> create GLOBAL TEMPORARY table gtt_test(a int);
Table created.
SQL> insert into gtt_test values(1);
1 row created.
SQL> commit;
Commit complete.
-- 因此提交后数据就没有了
SQL> select count(*) from gtt_test;
COUNT(*)
----------
0
-- 建立提交时保留数据的GTT
SQL> drop table gtt_test;
Table dropped.
SQL> create GLOBAL TEMPORARY table gtt_test(a int) ON COMMIT PRESERVE ROWS;
Table created.
SQL> insert into gtt_test values(1);
1 row created.
SQL> commit;
Commit complete.
SQL> select count(*) from gtt_test;
COUNT(*)
----------
1
-- 因为有人在使用,因此GTT无法删除。详见MOS Doc ID 800506.1
SQL> drop table gtt_test;
drop table gtt_test
*
ERROR at line 1:
ORA-14452: attempt to create, alter or drop an index on temporary table already
in use
-- 切换会话,数据没有了
SQL> connect ssb/Welcome1@orclpdb1
Connected.
SQL> select count(*) from gtt_test;
COUNT(*)
----------
0
PTT的实验
PTT表名有一定的命名规则:
SQL> show parameter PRIVATE_TEMP_TABLE_PREFIX
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
private_temp_table_prefix string ORA$PTT_
建立默认配置(ON COMMIT DROP DEFINITION)的PTT:
SQL> create PRIVATE TEMPORARY table ora$ptt_test(a int);
Table created.
SQL> insert into ora$ptt_test values(1);
1 row created.
SQL> commit;
Commit complete.
-- 事务提交后,表定义没有了
SQL> desc ora$ptt_test;
ERROR:
ORA-04043: object ora$ptt_test does not exist
建立会话结束后删除表定义,但提交后仍保留表定义的PTT:
SQL> create PRIVATE TEMPORARY table ora$ptt_test(a int) ON COMMIT PRESERVE DEFINITION;
Table created.
SQL> insert into ora$ptt_test values(1);
1 row created.
SQL> commit;
Commit complete.
SQL> desc ora$ptt_test;
Name Null? Type
----------------------------------------- -------- ----------------------------
A NUMBER(38)
SQL> connect ssb/Welcome1@orclpdb1
Connected.
SQL> desc ora$ptt_test;
ERROR:
ORA-04043: object ora$ptt_test does not exist
临时表的undo与redo
本实验参考Oracle BASE的文章:Global Temporary Tables
先来看临时表的undo:
SET AUTOTRACE ON STATISTICS;
create GLOBAL TEMPORARY table gtt_test(a int);
insert into gtt_test select 1 from dual connect by level < 10000;
9999 rows created.
Statistics
----------------------------------------------------------
13 recursive calls
138 db block gets
27 consistent gets
0 physical reads
28528 redo size
195 bytes sent via SQL*Net to client
415 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
9999 rows processed
SQL>
SELECT t.used_ublk,
t.used_urec
FROM v$transaction t,
v$session s
WHERE s.saddr = t.ses_addr
AND s.audsid = SYS_CONTEXT('USERENV', 'SESSIONID');
USED_UBLK USED_UREC
---------- ----------
4 55
V$TRANSACTION
的说明见这里,V$SESSION
的帮助见这里。USED_UBLK和USED_UREC分别表示undo block和undo record的数量。
如果将临时表换为永久表,重复以上的实验,结果是一样的。
虽然 GTT 中的数据写入临时表空间,但关联的 undo 仍然写入正常的 undo 表空间,该表空间本身受redo保护,因此使用 GTT 并不会减少与保护 undo 表空间相关联的 undo 和重做。
在12c,可以将undo写到临时表空间,参看这里。这篇文章也列举了这么做的好处:
- 写入undo表空间需要数据库以读写方式打开,所以不能在只读数据库和物理备数据库中使用全局临时表。
- 全局临时表包含临时数据,在恢复场景中不需要这些数据,因此使用redo保护它们意味着对系统造成不必要的额外负载。
- 与全局临时表关联的undo增加了满足undo保留期所需的总空间。
此行为由参数控制,默认不启用:
SQL> show parameter TEMP_UNDO_ENABLED
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
temp_undo_enabled boolean FALSE
ALTER SESSION SET TEMP_UNDO_ENABLED = TRUE;
SET AUTOTRACE ON STATISTICS;
create GLOBAL TEMPORARY table gtt_test(a int);
insert into gtt_test select 1 from dual connect by level < 10000;
9999 rows created.
Statistics
----------------------------------------------------------
11 recursive calls
143 db block gets
25 consistent gets
0 physical reads
280 redo size
195 bytes sent via SQL*Net to client
415 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
9999 rows processed
SQL>
SELECT t.used_ublk,
t.used_urec
FROM v$transaction t,
v$session s
WHERE s.saddr = t.ses_addr
AND s.audsid = SYS_CONTEXT('USERENV', 'SESSIONID');
USED_UBLK USED_UREC
---------- ----------
1 1
和未开启参数前相比,undo和redo都下降了很多。
临时表与并行执行
本实验参考Oracle Blogs的文章:Parallel PL/SQL Functions and Global Temporary Tables… and Wrong Results
作者的这个例子设计得非常巧妙,我在此基础上做了修改。行数改为1000,以缩短等待时间。sleep函数也做了修改。
-- 测试表中的rpad函数可以去掉,但测试结果就有可能没那么完美
create table s as select rownum id,rpad('X',1000) pad
from dual connect by level<=1000;
create or replace function f_wait(id in number) return number is
begin
dbms_session.sleep(0.01);
return(id);
end;
/
测试,无论在SQL中是否启用并行,结果都近似。从执行计划可知并行并未实际启用:
SQL> select count(*) from s where id=f_wait(id);
COUNT(*)
----------
1000
Elapsed: 00:00:13.78
SQL> select /*+ parallel(4) */ count(*) from s where id=f_wait(id);
COUNT(*)
----------
1000
Elapsed: 00:00:13.45
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 4 | 3 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 4 | | |
|* 2 | TABLE ACCESS FULL| S | 1 | 4 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------
这是由于,PL/SQL函数要支持并行需要显式定义。
create or replace function f_wait(id in number) return number parallel_enable is
begin
dbms_session.sleep(0.01);
return(id);
end;
/
现在,并行生效了,虽然不是完美的2.5秒:
SQL> select /*+ parallel(4) */ count(*) from s where id=f_wait(id);
COUNT(*)
----------
1000
Elapsed: 00:00:03.08
Execution Plan
----------------------------------------------------------
Plan hash value: 2247559131
----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 4 | 12 (0)| 00:00:01 | | | |
| 1 | SORT AGGREGATE | | 1 | 4 | | | | | |
| 2 | PX COORDINATOR | | | | | | | | |
| 3 | PX SEND QC (RANDOM) | :TQ10000 | 1 | 4 | | | Q1,00 | P->S | QC (RAND) |
| 4 | SORT AGGREGATE | | 1 | 4 | | | Q1,00 | PCWP | |
| 5 | PX BLOCK ITERATOR | | 1 | 4 | 12 (0)| 00:00:01 | Q1,00 | PCWC | |
|* 6 | TABLE ACCESS FULL| S | 1 | 4 | 12 (0)| 00:00:01 | Q1,00 | PCWP | |
----------------------------------------------------------------------------------------------------------------
现在进入正题,来谈启用并行的 PL/SQL 函数和全局临时表。
create GLOBAL TEMPORARY table gtt_test(a int);
insert into gtt_test select 1 from dual connect by level < 10000;
select /*+ parallel(2) */ count(*) from gtt_test;
COUNT(*)
----------
9999
在这种情况下,我们有两个 PX 服务器扫描临时表,并且报告的计数是正确的。 这表明单个 PX 服务器能够看到用户会话之前填充的数据。 并行查询的不同之处在于,在临时表上工作的并行会话可以看到之前由 QC 填充的数据。 当查询临时表时,QC 知道临时表并将分段信息发送到 PX 服务器,以便它们可以读取数据。
查询临时表的 PL/SQL 函数改变了这种行为,此时结果就不正确了:
create table t1 (id number);
insert into t1 values (1000);
commit;
create global temporary table tempstage (col1 number) on commit preserve rows;
create or replace function f_test return number parallel_enable is
v_var number;
begin
select col1 into v_var from tempstage;
return v_var;
end;
/
insert into tempstage values (100);
commit;
-- 错误的结果
SQL> select /*+ parallel(2) */ * from t1 where id>f_test;
-- 正确的结果
SQL> select * from t1 where id>f_test;
ID
----------
1000
它向用户返回了错误的结果。 这是因为该函数被声明为可以安全地由各个 PX 服务器执行。 每个 PX 服务器都使用自己的会话,因此它们无法看到用户会话填充的数据。 这与前面针对临时表运行查询的示例不同,在这种情况下,QC 知道涉及到临时表,这里它只看到启用了并行的函数调用。
因此,在将函数声明为启用并行时要小心,注意该函数将由 PX 服务器执行,这可能会导致一些意外行为。 考虑函数在由多个会话和进程执行时的行为方式。 仅当您确定它是安全的时才将其声明为启用并行。
参考
- https://blogs.oracle.com/optimizer/post/global-temporary-tables-and-upgrading-to-oracle-database-12c-dont-get-caught-out
以上是关于Oracle全局临时表和私有临时表的主要内容,如果未能解决你的问题,请参考以下文章