【问题标题】:Oracle external tables. Optimising select queriesOracle 外部表。优化选择查询
【发布时间】:2009-10-20 17:43:59
【问题描述】:

我必须从 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 的数据),而我们可以从他那里得到的只有外部表。他不允许我们创建临时表。我不会假装质疑他的理由,但外部表不是解决这个问题的方法。所以,我们被他们困住了。

【问题讨论】:

  • 听起来你被绑着双臂被扔到海里,被告知要游到岸边;-)
  • DBA 甚至不允许全局临时表?完全会话瞬态数据 - 会话结束时消失。
  • 您只是对外部表进行查询,还是包含数据库中已有的数据?如果您只是使用自己的数据,我会在您的计算机上加载类似 Oracle XE 的东西并使用它。

标签: sql oracle plsql external-tables


【解决方案1】:

制作Oracle 表格。

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

只要您对文件进行基础更改,只需运行一个导入脚本,这会将外部表的内容加载到Oracle 表中。

这是你的同名想法(从here窃取):

您使用的是外部表而不是sqlldr

你可以使用外部表

  • 在一个语句中将平面文件与现有表合并。
  • 将途中的平面文件排序到您想要很好地压缩的表格中。
  • 进行并行直接路径加载——不拆分输入文件,写入 无数的脚本等等
  • 在存储过程或触发器中运行sqlldr(插入不是sqlldr
  • 进行多表插入
  • 通过管道化的 plsql 函数传输数据以进行清理/转换

等等。它们是而不是sqlldr——将数据放入数据库,而不必首先使用sqlldr

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

更新:

3GB 表永远不会有不错的性能,因为Oracle 必须对每个查询执行3GB 全扫描,这将是一流的磁盘读取主轴移动全扫描,而不是您可以在计划中看到但在实际执行时间中几乎看不到的廉价缓存模仿。

试着说服那个人为你创建一个临时表,你可以用它来处理数据,并在你的会话开始时从外部表中加载数据。

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

【讨论】:

  • 我很想 +1,但不幸的是它不满足 OP 的要求。不过,这个建议是合理的。
【解决方案2】:

如果您必须解决没有意义但无法更改的限制,那真的很难......

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

所以你的光标应该是这样的:

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;

因此,您无需为每个寄存器循环遍历外部表,而只需为所有寄存器循环一次。

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

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      【解决方案4】:

      虽然完全同意 Quassnoi 的建议,即外部表在这里似乎不是正确的解决方案,以及 DCookie 的类比,即您被束缚并被抛到海里并被要求游泳,但至少可能有一种方法来构建您的程序,以便外部表只读取一次。根据您的描述,我相信所有 10 个游标都从外部表中读取,这意味着您正在强制 Oracle 扫描外部表 10 次。

      假设这个推断是正确的,最简单的答案可能是让外部表成为驱动光标,类似于 IronGoofy 的建议。根据下面代码 sn-p 中的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 语句)并移入驱动游标应该更有效。使用上面更简单的代码 sn-ps(当然,您可以将 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 子句一次一百行应该会使事情更有效率。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-06-03
        • 1970-01-01
        • 1970-01-01
        • 2011-04-11
        • 1970-01-01
        • 1970-01-01
        • 2016-01-23
        相关资源
        最近更新 更多