避免在Oracle中使用全局临时表的方法

25
我们刚将SQL Server存储过程转换为Oracle存储过程。SQL Server的存储过程高度依赖会话表格(INSERT INTO #table1...),这些表格在Oracle中被转换为全局临时表格。我们为400个存储过程创建了约500个全局临时表格。
目前我们发现,在Oracle中使用全局临时表格被认为是最后的选择,因为这会导致性能和其他问题。
那么还有其他替代方案吗?集合?游标?
我们典型地使用全局临时表格的方式是:
Insert into 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中获取数据。这些只是示例查询,实际上查询非常复杂,涉及许多连接和子查询。

我有一个由三部分组成的问题:

  1. 能否展示如何将上述示例查询转换为集合和/或游标?
  2. 既然使用GTT可以原生地使用SQL...为什么要离开GTT?它们真的那么糟糕吗?
  3. GTT的使用和避免应遵循什么准则?
2个回答

32

先回答第二个问题:

"为什么要远离GTTs?它们真的那么糟糕吗?"

几天前,我正在制作一个概念验证,将一个较大的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查询解决,那么全局临时表也很好用。特别是如果该处理必须应用于检索行的子集。

但总的来说,我们应该默认情况下不需要使用临时表。因此:

  1. 除非太难,否则请使用SQL完成
  2. 除非占用太多内存,否则请使用PL/SQL变量(通常是集合)
  3. 如果以上两种方法均不可行,请使用全局临时表

感谢APC提供详细的解释。我知道你事先警告说集合代码未经测试,但每次尝试运行代码时都会出现“值不足”的错误。你能告诉我出了什么问题吗?当我浏览GUI时...tab_a_nt没有属性,而tab_a_row有3个(如定义)。 - Omnipresent
我真的很困惑,无法让它正常工作。你能帮我解决问题吗?http://stackoverflow.com/questions/3036948/confusion-about-using-types-instead-of-gtts-in-oracle - Omnipresent

4
通常我会使用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;
/

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接