【问题标题】:Performance issue updating 9 million records using a script使用脚本更新 900 万条记录的性能问题
【发布时间】:2020-04-30 16:17:12
【问题描述】:

我们一直在运行下面的脚本来更新运行 11G oracle DB (11.2.0.3) 的表中的一些列,这需要大约 61 小时才能完成,这令人惊讶,因为我们使用 Bulk collect 和 Forall 来执行实际操作更新。我们还启用了并行 dml。我们还尝试基于 rowid 进行更新,而不是使用索引的列,因为我们认为这样会更快。任何加快速度的建议都会很棒。下面是脚本

ALTER session enable parallel dml;
DECLARE
i NUMBER;
j number :=0 ;
TYPE tab_type IS TABLE OF rowid index by binary_integer;
tab_id tab_type;

CURSOR c1 IS
SELECT /*+ parallel(na,DEFAULT) */
                   rowid
                             from sample_table na
                             FOR UPDATE SKIP LOCKED;
BEGIN
  OPEN c1;
  LOOP
    FETCH c1 BULK COLLECT INTO tab_id LIMIT 10000;
    EXIT WHEN tab_id.COUNT = 0;

    FORALL i IN 1..tab_id.COUNT
                             update sample_table 
        set col1 = 'XXX'
        , col2 = 'XXX'
        , col3 = 'XXX'
        , col4 = 'XXX'
                             , col5= 'XXX'
        , col6 = 'XXX' 
     WHERE rowid = tab_id(i);
              j := j+1;
              if mod(j, 1000) = 0 THEN    -- Commit every 1000 records
                    COMMIT;
              end if;


  END LOOP;

  CLOSE c1;

END;
/

【问题讨论】:

    标签: sql plsql oracle11g


    【解决方案1】:

    我不完全确定这会对您的运行时间产生影响,但我不能伤害它。此外,您的代码表明了一些误解。

    1. 首先FORALL 语句不会创建循环。它运行 单个合并 1 次,处理整个集合。
    2. 这也意味着您的提交间隔不是您所指出的 1000,而是 1M。
    3. 语句中的索引变量 (i) 是本地的 声明并且只能在forall范围内访问 陈述。所以声明的变量 i 不是使用的变量 forall,因此不需要。没有错误是因为 范围规则。
    4. 由于退出循环后没有提交,最后一组将 除非行数是 提交间隔。在您的情况下,如果您有 8,999,999 行,那么提交间隔为 1M,那么只有 8M 会被提交。

    考虑到这一切,你可以试试:

    declare
      type tab_type is table of rowid;
      tab_id tab_type;
    
      k_buffer_limit constant pls_integer  := 10000;
    
     cursor c1 is
            select /*+ parallel(na,DEFAULT) */
                   rowid
              from sample_table na
               for update skip locked;
    begin
      open c1;
      loop
        fetch c1 bulk collect into tab_id limit 10000
    
        forall i in 1..tab_id.count
          update sample_table 
             set col1 = 'XXX'
               , col2 = 'XXX'
               , col3 = 'XXX'
               , col4 = 'XXX'
               , col5=  'XXX'
               , col6 = 'XXX' 
          where rowid = tab_id(i);
    
         commit;    
         exit when tab_id.count < k_buffer_limit; 
      end loop;
    
      close c1;
    
    end;
    
    1. 批量收集/Forall 处理在上下文切换之间进行权衡 和内存使用情况。虽然减少上下文切换是一件好事,但 可能会被内存需求所克服。你的过程可能是 进入等待以获得足够的内存。通过lowering the buffer size,您可能会获得更好的性能。

    【讨论】:

    • 非常感谢您的建议。我认为在关闭游标后必须移动提交,因为它可能会导致提取乱序错误。
    • 一次提交不会进行另一次获取。它可以在关闭光标之前或之后进行,并且两种方式都没有效果。提交既不知道也不影响光标,因为它们解决了跨提交的光标不稳定问题(大约在 v9?或 v10?那里的某个地方)。
    • 感谢您澄清这一点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-14
    • 1970-01-01
    相关资源
    最近更新 更多