使用rowid从一个表到另一个Oracle更新百万行

时间:2013-06-17 20:21:05

标签: sql database oracle plsql

嗨我有两个表,每行有一百万行。我有oracle 11 g R1 我相信我们中的许多人都必须经历这种情况。

从一个表更新到另一个表的值是不同的最快捷方式是什么。

例如:表1有4个NUMBER列,具有高精度,例如:0.2212454215454212

表2有6列。 更新表2的四列基于两个表上的公共列,只有不同的列。

我有类似的东西

DECLARE
TYPE test1_t IS TABLE OF test.score%TYPE INDEX BY PLS_..;
TYPE test2_t IS TABLE OF test.id%TYPE INDEX BY PLS..; 
TYPE test3_t IS TABLE OF test.Crank%TYPE INDEX BY PLS..;

vscore test1_t;
vid test2_t;
vurank test4_t;

BEGIN
  SELECT id,score,urank
    BULK COLLECT INTO vid,vscore,vurank
    FROM test;

  FORALL i IN 1 .. vid.COUNT
    MERGE INTO final T
      USING (SELECT vid (i) AS o_id,
                    vurank (i) AS o_urank,
                    vscore (i) AS o_score FROM DUAL) S
      ON (S.o_id = T.id)
    WHEN MATCHED THEN
      UPDATE SET T.crank = S.o_crank
      WHERE T.crank <> S.o_crank;

由于数字具有高精度,它是否会减慢?

如果我必须更新100万行,我尝试批量收集和合并组合仍然花费时间约30分钟用于最坏情况。

有什么与rowid? 将不胜感激。

4 个答案:

答案 0 :(得分:2)

如果要更新所有行,请使用update:

update table_1
set    (col1,
        col2) = (
         select col1,
                col2
         from   table2
         where  table2.col_a = table1.col_a and
                table2.col_b = table1.col_b)

批量收集或任何PL / SQL技术总是比纯SQL技术慢。

数值精度可能不重要,而且rowid不相关,因为两个表之间没有共同的值。

答案 1 :(得分:2)

当处理数百万行时,并行DML是游戏规则改变者。当然,你需要让Enterprise Edition使用并行,但它确实是唯一会产生很大差异的东西。

我建议您通过rleishman比较8 Bulk Update Methods阅读一篇关于OraFAQ的文章。他的主要发现是“到目前为止,磁盘读取的成本超过了它们几乎不可察觉的上下文切换(原文如此)”。换句话说,除非您的数据已经缓存在内存中,否则SQL和PL / SQL方法之间确实没有显着差异。

这篇文章确实有一些关于使用并行的简洁建议。令人惊讶的结果是并行流水线功能提供了最佳性能。

答案 2 :(得分:1)

关注语法并跳过逻辑(可能使用纯更新+纯插入可以解决问题,合并成本,索引,合并时可能的全扫描等)
您应该在批量收集语法中使用 Limit 使用无限制的批量收集

  1. 将所有记录加载到内存中
  2. 如果没有部分提交的合并,您将创建一个大型redolog, 必须在流程结束时适用。
  3. 两者都会导致性能低下。

    DECLARE
     v_fetchSize NUMBER := 1000; -- based on hardware, design and .... could be scaled
     CURSOR a_cur IS 
     SELECT id,score,urank FROM test;    
     TYPE myarray IS TABLE OF a_cur%ROWTYPE;
     cur_array myarray;
    
        BEGIN
          OPEN a_cur;
          LOOP
            FETCH a_cur BULK COLLECT INTO cur_array LIMIT v_fetchSize;
              FORALL i IN 1 .. cur_array.COUNT
              // DO Operation
              COMMIT;
            EXIT WHEN a_cur%NOTFOUND;
          END LOOP;
          CLOSE a_cur;
        END;
    

答案 3 :(得分:0)

  1. 只是为了确保:test.idfinal.id必须编入索引。

  2. 首先select ... from test您从Table 1获得了太多记录,之后您需要将所有记录与Table 2上的记录进行比较。尝试仅选择您需要更新的内容。因此,至少有两种变体:

  3. a)仅选择更改的记录:

      SELECT source_table.id, source_table.score, source_table.urank 
      BULK COLLECT INTO vid,vscore,vurank 
      FROM 
        test source_table, 
        final destination_table
      where 
        source_table.id = destination_table.id 
        and
        source_table.crank <> destination_table.crank
       ;
    

    b)使用datetime值向源表添加新字段,并将其填入当前时间的触发器中。同步选择仅在最后一天更改的记录。该字段需要编入索引。

    在更新阶段进行此类更改后,您无需比较其他字段,只匹配ID:

      FORALL i IN 1 .. vid.COUNT 
      MERGE INTO FINAL T 
      USING (
        SELECT vid (i) AS o_id,
               vurank (i) AS o_urank,
               vscore (i) AS o_score FROM DUAL
      ) S 
      ON (S.o_id = T.id) 
      WHEN MATCHED 
      THEN UPDATE SET T.crank = S.o_crank 
    

    如果您担心撤消/重做段的大小,那么变体b)会更有用,因为您可以将源Table 1中的记录划分为时间片,并在更新每个片后提交更改。例如。从00:00到01:00,从01:00到02:00等 在此变体中,只需通过SQL语句即可完成更新,而无需在行中保存可接受大小的重做/撤消日志的数据。