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

Posted

技术标签:

【中文标题】在 oracle 中避免全局临时表的方法【英文标题】:ways to avoid global temp tables in oracle 【发布时间】:2011-02-24 11:56:24 【问题描述】:

我们刚刚将我们的 sql server 存储过程转换为 oracle 过程。 Sql Server SP 高度依赖会话表 (INSERT INTO #table1...),这些表在 oracle 中被转换为全局临时表。我们的 400 个 SP 最终获得了大约 500 个 GTT

现在我们发现,由于性能和其他问题,在 oracle 中使用 GTT 被认为是最后的选择。

还有哪些其他选择?收藏?光标?

我们对 GTT 的典型用法是这样的:

插入 GTT

INSERT INTO some_gtt_1
  (column_a,
   column_b,
   column_c)
  (SELECT someA,
      someB,
      someC
     FROM TABLE_A
    WHERE condition_1 = 'YN756'
      AND type_cd = 'P'
      AND TO_NUMBER(TO_CHAR(m_date, 'MM')) = '12'
      AND (lname LIKE (v_LnameUpper || '%') OR
      lname LIKE (v_searchLnameLower || '%'))
      AND (e_flag = 'Y' OR
      it_flag = 'Y' OR
      fit_flag = 'Y'));

更新 GTT

UPDATE some_gtt_1 a
SET column_a = (SELECT b.data_a FROM some_table_b b 
               WHERE a.column_b = b.data_b AND a.column_c = 'C')
WHERE column_a IS NULL OR column_a = ' ';

然后从 GTT 中获取数据。这些只是示例查询,实际上查询非常复杂,有很多连接和子查询。

我有一个三部分的问题:

    谁能说明如何转换 上述示例查询 集合和/或游标? 自 使用 GTT,您可以在本地工作 使用 SQL...为什么要远离 GTT?他们真的那么糟糕吗? 应该是什么准则 何时使用以及何时避免使用 GTT

【问题讨论】:

【参考方案1】:

我们先回答第二个问题:

“为什么要离开 GTT?他们是 真的很糟糕。”

几天前,我正在做一个概念证明,它将一个较大的 XML 文件 (~18MB) 加载到一个 XMLType 中。因为我不想永久存储 XMLType,所以我尝试将它加载到 PL/SQL 变量(会话内存)和临时表中。将其加载到临时表中所花费的时间是将其加载到 XMLType 变量中的时间的五倍(5 秒与 1 秒相比)。不同之处在于临时表不是内存结构:它们被写入磁盘(特别是您指定的临时表空间)。

如果您想缓存大量数据,则将其存储在内存中会给 PGA 带来压力,如果您有很多会话,这将是不好的。所以这是 RAM 和时间之间的权衡。

到第一个问题:

“有人可以展示如何转换 以上对集合的示例查询 和/或光标?”

您发布的查询可以合并为一条语句:

SELECT case when a.column_a IS NULL OR a.column_a = ' ' 
           then b.data_a
           else  column_a end AS someA,
       a.someB,
       a.someC
FROM TABLE_A a
      left outer join TABLE_B b
          on ( a.column_b = b.data_b AND a.column_c = 'C' )
WHERE condition_1 = 'YN756'
  AND type_cd = 'P'
  AND TO_NUMBER(TO_CHAR(m_date, 'MM')) = '12'
  AND (lname LIKE (v_LnameUpper || '%') OR
  lname LIKE (v_searchLnameLower || '%'))
  AND (e_flag = 'Y' OR
  it_flag = 'Y' OR
  fit_flag = 'Y'));

(我只是转换了您的逻辑,但 case() 语句可以替换为更简洁的 nvl2(trim(a.column_a), a.column_a, b.data_a) )。

我知道您说您的查询更复杂,但您的首要任务应该是考虑重写它们。我知道将一个粗糙的查询分解成许多用 PL/SQL 拼接在一起的小 SQL 是多么诱人,但纯 SQL 效率更高。

要使用集合,最好在 SQL 中定义类型,因为它使我们可以灵活地在 SQL 语句以及 PL/SQL 中使用它们。

create or replace type tab_a_row as object
    (col_a number
     , col_b varchar2(23)
     , col_c date);
/
create or replace type tab_a_nt as table of tab_a_row;
/

这是一个示例函数,它返回一个结果集:

create or replace function get_table_a 
      (p_arg in number) 
      return sys_refcursor 
is 
    tab_a_recs tab_a_nt; 
    rv sys_refcursor; 
begin 
    select tab_a_row(col_a, col_b, col_c)  
    bulk collect into tab_a_recs 
    from table_a 
    where col_a = p_arg; 

    for i in tab_a_recs.first()..tab_a_recs.last() 
    loop 
        if tab_a_recs(i).col_b is null 
        then 
            tab_a_recs(i).col_b :=  'something'; 
        end if; 
    end loop;  

    open rv for select * from table(tab_a_recs); 
    return rv; 
end; 
/ 

它正在发挥作用:

SQL> select * from table_a
  2  /

     COL_A COL_B                   COL_C
---------- ----------------------- ---------
         1 whatever                13-JUN-10
         1                         12-JUN-10

SQL> var rc refcursor
SQL> exec :rc := get_table_a(1)

PL/SQL procedure successfully completed.

SQL> print rc

     COL_A COL_B                   COL_C
---------- ----------------------- ---------
         1 whatever                13-JUN-10
         1 something               12-JUN-10

SQL>

在函数中,必须用列实例化类型,以避免 ORA-00947 异常。这在填充 PL/SQL 表类型时不是必需的:

SQL> create or replace procedure pop_table_a
  2        (p_arg in number)
  3  is
  4      type table_a_nt is table of table_a%rowtype;
  5      tab_a_recs table_a_nt;
  6  begin
  7      select *
  8      bulk collect into tab_a_recs
  9      from table_a
 10      where col_a = p_arg;
 11  end;
 12  /

Procedure created.

SQL> 

最后是指南

“什么时候应该有什么指导方针 使用和何时避免使用 GTT"

当我们需要在同一会话中的不同程序单元之间共享缓存数据时,全局临时表非常有用。例如,如果我们有一个由单个函数生成的通用报告结构,该结构由一个 GTT 馈送,该 GTT 由几个过程之一填充。 (虽然即使这样也可以用动态引用游标来实现......)

如果我们有很多中间处理,而这些中间处理太复杂而无法通过单个 SQL 查询来解决,那么全局临时表也很好。特别是如果该处理必须应用于检索到的行的子集。

但总的来说,我们的假设应该是我们不需要使用临时表。所以

    在 SQL 中执行,除非在哪种情况下太难... ...在 PL/SQL 变量(通常是集合)中执行此操作,除非在这种情况下占用太多内存... ... 使用全局临时表进行操作

【讨论】:

感谢 APC 的详细解释。我知道您预先警告过集合的代码未经测试,但是当我尝试运行代码时总是得到“没有足够的值”。你能告诉我有什么问题吗?当我查看 gui ...tab_a_nt 没有属性,而 tab_a_row 有 3 个(定义)。 我真的很困惑,无法让它工作。你能让我渡过难关吗? ***.com/questions/3036948/…【参考方案2】:

通常我会使用 PL/SQL 集合来存储少量数据(可能是一千行)。如果数据量更大,我会使用 GTT,这样它们就不会使进程内存过载。

所以我可能会从数据库中选择几百行到一个 PL/SQL 集合中,然后遍历它们以进行一些计算/删除一些或其他什么,然后将该集合插入到另一个表中。

如果我要处理数十万行,我会尝试将尽可能多的“繁重”处理推到大型 SQL 语句中。这可能需要也可能不需要 GTT。

您可以将 SQL 级别的集合对象用作在 SQL 和 PL/SQL 之间轻松转换的东西

create type typ_car is object (make varchar2(10), model varchar2(20), year number(4));
/

create type typ_coll_car is table of typ_car;
/

select * from table (typ_coll_car(typ_car('a','b',1999), typ_car('A','Z',2000)));
MAKE       MODEL                           YEAR
---------- -------------------- ---------------
a          b                           1,999.00
A          Z                           2,000.00

declare
  v_car1 typ_car := typ_car('a','b',1999);
  v_car2 typ_car := typ_car('A','Z',2000);
  t_car  typ_coll_car := typ_coll_car();
begin
  t_car := typ_coll_car(v_car1, v_car2);
  FOR i in (SELECT * from table(t_car)) LOOP
    dbms_output.put_line(i.year);
    END LOOP;
end;
/

【讨论】:

以上是关于在 oracle 中避免全局临时表的方法的主要内容,如果未能解决你的问题,请参考以下文章

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

如何使用 Oracle 全局临时表?

Oracle 12C 新特性之临时undo控制

sql server中的用户临时表和全局临时表的区别

oracle存储过程中临时表的使用,该怎么处理

SQL怎么创建一个临时表