Oracle外部表。优化选择查询

时间:2009-10-20 17:43:59

标签: sql oracle plsql external-tables

我必须从Oracle外部表中执行许多选择。

我有10个看起来很像这样的游标(ext_temp是外部表)

CURSOR F_CURSOR (day IN varchar,code Number,orig Number)
    IS
    select NVL(sum(table_4.f),0) 
     from ext_temp table_4
    where
      --couple of conditions here, irrelevant for the question at hand.
      AND TO_CHAR(table_4.day,'YYYYMMDD') = day
      AND table_4.CODE = code
      AND table_4.ORIG = orig;

外部表有大约22659个寄存器。

我的脚本主循环看起来像这样

   for each register in some_query: --22659 registers
       open F_cursor(register.day,register.code,register.orig);
       --open 9 more cursors

       fetch F_cursor into some_var;  
       --fetch 9 more cursors, with the same structure

查询已经取得了很大进展。我从here知道我不能有任何索引或DML。

那么,有没有办法让它跑得更快?我可以重写我的plsql脚本,但我不认为我还有时间。

更新:遗漏了一个重要细节。

我不是数据库的所有者或DBA。那家伙不希望在他的数据库中有任何额外的信息(大约3gb的数据),外部的表格是我们可以从他身上得到的。他不允许我们创建临时表。我不假装质疑他的理由,但外部表不是解决方案。所以,我们坚持使用它们。

4 个答案:

答案 0 :(得分:4)

制作Oracle个表格。

外部表格可以替换SQL*LOADER,而不是每天都使用它们。

只要基础文件发生更改,就会运行导入脚本,这会将外部表的内容加载到Oracle表中。

这是你的同名所想的(从here偷来的):

  

您正在使用外部表而不是 sqlldr

     

使用外部表格

     
      
  • 在一个语句中将平面文件与现有表合并。
  •   
  • 在您想要压缩的表格的路上对平面文件进行排序。
  •   
  • 执行并行直接路径加载 - 不分割输入文件,写入   无数脚本等
  •   
  • 从存储过程或触发器生效sqlldr(插入不是sqlldr
  •   
  • 执行多表插入
  •   
  • 通过流水线plsql函数流式传输数据以进行清理/转换
  •   
     

等等。他们是而不是 sqlldr - 无需首先使用sqlldr即可将数据导入数据库。

     

您通常不会在操作系统中每天查询它们,而是使用它们来加载数据。

<强>更新

使用3GB表格无法获得良好的性能,因为Oracle必须对每个查询执行3GB全扫描,并且它将是一流的磁盘 - 读取主轴移动的全扫描,而不是一个廉价的缓存模仿,你可以在计划中看到,但在实际执行时间内几乎不会注意到。

尝试说服该人为您创建一个临时表,您可以使用该表来处理数据,只需在会话开始时从外部表加载数据。

这不是最好的解决方案,因为它需要为临时表空间中的每个会话保留表的单独副本,但它在性能方面要好得多。

答案 1 :(得分:3)

如果你必须解决那些没有意义但你无法改变的限制,这真的很难......

您应该最好通过外部表读取一次,然后在代码中的类索引数据结构中构建所需的数据(基本上是一个数组,为您要查找的每个寄存器都有一个元素)。

所以你的光标看起来像这样:

CURSOR F_CURSOR (day IN varchar, orig IN Number)
    IS
    select NVL(sum(table_4.f),0) value, table_4.CODE register
     from ext_temp table_4
    where
      --couple of conditions here, irrelevant for the question at hand.
      AND TO_CHAR(table_4.day,'YYYYMMDD') = day
      -- AND table_4.CODE = code -- don't use this condition!
      AND table_4.ORIG = orig;

你的寄存器循环将变成一个游标循环:

open F_cursor(register.day,register.orig);
LOOP
    fetch F_cursor into some_var;
    EXIT WHEN F_cursor%NOT_FOUND
    result (some_var.register) := some_var.value;
END LOOP;

结果,不是每个寄存器通过外部表循环,而是只需要一个循环用于所有寄存器。

这可以扩展到你提到的十个游标。

答案 2 :(得分:0)

您可以将外部表数据写入temporary索引(如果需要)表,然后针对它执行多个查询。

create your_temp_table as select * from ext_temp;
create index your_desired_index on your_temp_table(indexed_field);

然后使用your_temp_table直接执行所有查询。

答案 3 :(得分:0)

虽然完全同意Quassnoi的建议,即外部桌子似乎不是这里的正确解决方案,以及DCookie的类比,你被绑在船上并被要求游泳,至少可能有一种结构方式您的程序,以便外部表只读一次。我从你的描述中得到的信念是,所有10个游标都在从外部表中读取,这意味着你强迫Oracle扫描外部表10次。

假设这种推断是正确的,最简单的答案可能是使外部表成为驱动光标,类似于IronGoofy的建议。取决于以下代码段中的some_query

for each register in some_query

并且假设查询返回外部表中相同行数的事实并非巧合,最简单的选择是执行类似

的操作
FOR register in (select * from ext_temp)
LOOP
  -- Figure out if the row should have been part of cursor 1
  IF( <<set of conditions>> ) 
  THEN
    <<do something>>
  -- Figure out if the row should have been part of cursor 2
  ELSIF( ... )
  ...
END LOOP;

FOR register in (select * 
                   from ext_temp a, 
                        (<<some query>>) b 
                  where a.column_name = b.column_name )
LOOP
  -- Figure out if the row should have been part of cursor 1
  IF( <<set of conditions>> ) 
  THEN
    <<do something>>
  -- Figure out if the row should have been part of cursor 2
  ELSIF( ... )
  ...
END LOOP;

进一步采取措施并将逻辑移出游标(和IF语句)并进入驱动光标应该更有效率。使用上面更简单的代码片段(当然,您可以将some_query加入这些示例

FOR register in (select a.*,
                        NVL(sum( (case when condition1 and condition2
                                       then table_4.f
                                       else 0
                                       end) ),
                             0) f_cursor_sum
                  from ext_temp table_4)
LOOP
  <<do something>>
END LOOP;

如果,即使在这样做之后,您仍然发现您正在进行一些逐行处理,您甚至可以向前迈出一步并从驱动光标执行BULK COLLECT到本地声明的集合并进行操作那个集合。你几乎肯定不想将3 GB的数据提取到本地集合中(虽然破坏PGA可能会导致DBA得出结论临时表不是一件坏事,这不是我建议的),取一些使用LIMIT子句一次一百行应该使事情更有效率。