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

Posted

技术标签:

【中文标题】使用 rowid 从一个表更新百万行到另一个 Oracle【英文标题】:Update million rows using rowids from one table to another Oracle 【发布时间】:2013-06-17 20:21:05 【问题描述】:

您好,我有两张表,每张都有百万行。我有 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 万行,我尝试了 Bulk Collect 和 Merge 组合仍然需要时间 ~ 30 分钟,最坏的情况是。

rowid 有什么东西吗? 我们将不胜感激。

【问题讨论】:

你检查过执行计划了吗? 表有索引吗?如果不尝试索引它们然后运行更新查询,这将比普通表更快 是的,我在所有 4 列和主键 col 上都有索引 不要在 cmets 中发布代码。请编辑您的问题 一百万行并不是一个大数据量。在快速测试中,我的桌面在 100 秒内更新了一个类似的表,并生成了 272MB UNDO 和 691MB REDO。在开始研究高级方法之前,请比较不同系统上明显的方法,并尝试找出差异的来源。即使有您的审计触发器,我也不希望性能如此糟糕。这意味着服务器速度慢、计划不佳或触发器实施不当。 【参考方案1】:

如果要更新所有行,只需使用 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 不相关,因为两个表之间没有共同值。

【讨论】:

但是我必须包含提交还是让它运行 100 万行?因为以这种方式更新会生成大量撤消和重做日志 另外我想知道触发器会进一步降低性能..我有一个触发器,可以将行放在 Journal 表中以用于表上发生的任何更新 没有重做和撤消就无法进行更新,并且当您通过选择部分提交时,您告诉 RDBMS 在提交点之前您不再需要任何读取一致性块形式的撤消 -然后可能会出现“快照太旧”错误。您需要适当地调整撤消的大小,并且会产生大量的重做,但这是在遇到媒体故障时能够从备份前滚的代价。 是的,如果您需要运行同时将行插入另一个表的触发器代码,从而导致更多的重做和撤消工作,您显然会降低性能。您是否考虑过创建要更新的表的新版本? 其实我是这么想的,但是他们想跟踪历史,所以有一个触发器。如果我每天都删除并创建表,那么增量更改将无法捕获【参考方案2】:

在处理数百万行时,并行 DML 可以改变游戏规则。当然你需要有企业版才能使用并行,但它确实是唯一会产生很大不同的东西。

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

这篇文章确实对使用并行提出了一些巧妙的建议。令人惊讶的结果是并行流水线函数提供了最佳性能。

【讨论】:

附录 1 在那里非常重要。一旦数据集之间的散列连接变得更有效,SQL 方法(无论是 MERGE 还是 UPDATE)都会向前飞跃。 @DavidAldridge - 这是非常中肯的。如果源表中的更新行数接近目标表中的行数,那么 SQL 方法的性能将优于 PL/SQL。 如果他在 11gR2 上,那么 DBMS_PARALLEL_EXECUTE 可能在这里有用。 +1 对于有趣的文章,我很想知道 DBMS_PARALLEL_EXECUTE 与其他方法的比较。虽然我认为这篇文章有一点的误导。从结果表来看,并行流水线函数显然是赢家。但正如附录 2 所示,这只是因为 DOP 太高。在如此极端的条件下比较两种方法是没有用的。 (这个 DOP 的东西很棘手,有人需要写一本关于它的书。)【参考方案3】:

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

    将所有记录都加载到内存中 没有部分提交的合并,您将创建一个大的重做日志, 必须在流程结束时应用。

两者都会导致性能低下。

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;

【讨论】:

使用LIMIT 当然可以帮助防止内存不足。但我希望它会略微降低性能,因为它引入了更多的上下文切换。将进程分成更小的块通常会降低性能。 (除非其中一些块可以同时运行。) 如果您可以对此事设置测试以比较结果,我们可以进行更多调查。 见this Steven Feuerstein script。回报是递减的,但不是负回报。我还修改了脚本以使用更大的值,但得到了相同的结果。【参考方案4】:

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

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

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) 使用日期时间值将新字段添加到源表中,并将其填充到当前时间的触发器中。在同步挑选时,仅记录在最后一天发生了变化。该字段需要被索引。

在更新阶段进行这样的更改后,您无需比较其他字段,只需匹配 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 语句即可完成更新,无需在行中选择数据到集合中,同时保持可接受的重做/撤消日志大小。

【讨论】:

以上是关于使用 rowid 从一个表更新百万行到另一个 Oracle的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SQL Server 中更新具有数百万行的大表?

如何从一个DataTable中复制数据行到另一个DataTable中

当 SQL Server 表中的列“createdDate”从现在起经过 90 天后,如何更新其具有数百万行的列?我们可以使用触发器吗?

表格集算表高性能原理——怎样实现纯前端百万行数据秒级响应

表格集算表高性能原理——怎样实现纯前端百万行数据秒级响应

具有数百万行的 Django 表