Oracle全局临时表和私有临时表

Posted dingdingfish

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Oracle全局临时表和私有临时表相关的知识,希望对你有一定的参考价值。

Oracle 全局临时表以下简称GTT(Global Temporary Table),私有临时表以下简称PTT(Private Temporary Table)。

GTT和PTT的主要区别如下:

  1. GTT更早。GTT是8i就有的特性,PTT是18c才有的特性。
  2. GTT和PTT的数据都是会话私有的,而且会话结束后数据就没有了,但GTT的定义是全局的。
  3. 事务提交时,GTT可以选择是否保留数据,PTT可以选择是否保留定义。
  4. 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全局临时表和私有临时表的主要内容,如果未能解决你的问题,请参考以下文章

在 oracle 中避免全局临时表的方法

查询成本:全局临时表与集合(虚拟数组)

SQL Server 表变量和临时表的区别

怎么修改mysql数据库临时表空间大小

临时表和全局临时表

数据库——创建局部临时表和全局临时表