【问题标题】:Efficient way to update all rows in a table更新表中所有行的有效方法
【发布时间】:2023-04-01 03:00:01
【问题描述】:

我有一张包含很多记录的表(可能超过 500 000 或 1 000 000)。我在此表中添加了一个新列,我需要使用此表中另一列的相应行值为该列中的每一行填充一个值。

我尝试使用单独的事务来选择每下一个 100 条记录块并更新它们的值,但是更新 Oracle10 中的所有记录仍然需要数小时。

在不使用某些特定方言的功能的情况下,在 SQL 中执行此操作的最有效方法是什么,因此它适用于任何地方(Oracle、MSSQL、MySQL、PostGre 等)?

附加信息:没有计算字段。有索引。使用生成的 SQL 语句逐行更新表。

【问题讨论】:

  • 如果INDEX es 出现在更新/插入的列中,很少有人会走到极端,让夜间工作对其进行分析。
  • 我们需要更多信息。告诉我们有关表架构的信息......任何“计算”列?有什么索引吗? 500k - 1m 行无论如何都不是很多记录。
  • 感谢大家的快速响应。我跳过了我使用生成的 SQL 语句的部分。现在我深入研究了它,它看起来像生成的 SQL 逐行更新!因此,任何将 100 条记录分开的尝试都是毫无意义的……我将更改代码以生成正确的 SQL UPDATE 语句,如已接受的答案所示。

标签: sql performance oracle


【解决方案1】:

通常的方式是使用UPDATE:

UPDATE mytable
   SET new_column = <expr containing old_column>

你应该能够做到这一点是一个单一的交易。

【讨论】:

  • 听起来OP知道如何在单个事务中执行此操作,但是存在性能问题,因此他尝试将其批处理到单独的事务中。
  • 这是可能的,但很不寻常的是 1 M 行需要这么长时间才能更新单个列。 OP 也有可能一次更新记录,要么是由于缺乏对集合操作的理解,要么是因为他们试图计算客户端代码中的新值(出于必要或再次,因为缺乏理解) .无论如何,如果 OP 指出上述哪种情况适用于他们,我将能够更新我的答案。
  • OP:如果是性能问题,请在安静的时间进行。如果您的 DBMS 无法处理一百万行的更新,那么是时候开始寻找新的 DBMS 了 :-)
  • 感谢大家的快速响应。我跳过了我使用生成的 SQL 语句的部分。现在我深入研究了它,它看起来像生成的 SQL 逐行更新!因此,任何将 100 条记录分开的尝试都是毫无意义的……我将更改代码以生成正确的 SQL UPDATE 语句,正如此处指出的那样。
  • 不错!对于生成的 SQL,这是一个史诗般的失败。 Marcelo 的解决方案做得很好。
【解决方案2】:

您可以删除表上的任何索引,然后进行插入,然后重新创建索引。

【讨论】:

  • +1。提出这个建议只是时间问题,但是是的,对于 10M 或更多的行,只要你能快速完成它们,你就可以做到。
  • 为了你所崇拜的任何神灵的爱,在安静的时间做这件事。否则你的用户会追踪你、折磨你、杀了你、把你切成四分、焦油和羽毛,然后烧掉它们并吐在你烧焦的身体部位上。最低限度。他们可能会做得更糟。
  • 听起来像是牺牲在性能祭坛上的 DBA 的呻吟声……
  • 如果新列没有被索引,删除表上的索引是没有用的(没关系,因为重建一个1M行的索引不会花费太多时间)
  • 同意。我已经要求 OP 提供更多细节。现在有这么多的解决方案猜测。
【解决方案3】:

可能不适合你,但我过去曾多次在类似情况下使用过这种技术。

created updated_{table_name},然后选择批量插入到这个表中。一旦完成,这取决于 Oracle(我不知道或使用它)支持以原子方式重命名表的能力。 updated_{table_name} 变为 {table_name} 而 {table_name} 变为 original_{table_name}。

上次我必须这样做是针对具有数百万行的大量索引表,在对其进行一些重大更改所需的持续时间内绝对无法锁定。

【讨论】:

    【解决方案4】:

    正如马塞洛所说:

    UPDATE mytable
    SET new_column = <expr containing old_column>;
    

    如果这需要太长时间并且由于“快照太旧”错误而失败(例如,如果表达式查询另一个高度活跃的表),并且如果列的新值始终为 NOT NULL,则可以更新表批次:

    UPDATE mytable
    SET new_column = <expr containing old_column>
    WHERE new_column IS NULL
    AND ROWNUM <= 100000;
    

    只需运行这条语句,COMMIT,然后再次运行它;冲洗,重复直到它报告“0行更新”。这需要更长的时间,但每次更新失败的可能性较小。

    编辑:

    应该更有效的更好选择是使用DBMS_PARALLEL_EXECUTE API。

    示例代码(来自 Oracle 文档):

    DECLARE
      l_sql_stmt VARCHAR2(1000);
      l_try NUMBER;
      l_status NUMBER;
    BEGIN
    
      -- Create the TASK
      DBMS_PARALLEL_EXECUTE.CREATE_TASK ('mytask');
    
      -- Chunk the table by ROWID
      DBMS_PARALLEL_EXECUTE.CREATE_CHUNKS_BY_ROWID('mytask', 'HR', 'EMPLOYEES', true, 100);
    
      -- Execute the DML in parallel
      l_sql_stmt := 'update EMPLOYEES e 
          SET e.salary = e.salary + 10
          WHERE rowid BETWEEN :start_id AND :end_id';
      DBMS_PARALLEL_EXECUTE.RUN_TASK('mytask', l_sql_stmt, DBMS_SQL.NATIVE,
                                     parallel_level => 10);
    
      -- If there is an error, RESUME it for at most 2 times.
      l_try := 0;
      l_status := DBMS_PARALLEL_EXECUTE.TASK_STATUS('mytask');
      WHILE(l_try < 2 and l_status != DBMS_PARALLEL_EXECUTE.FINISHED) 
      LOOP
        l_try := l_try + 1;
        DBMS_PARALLEL_EXECUTE.RESUME_TASK('mytask');
        l_status := DBMS_PARALLEL_EXECUTE.TASK_STATUS('mytask');
      END LOOP;
    
      -- Done with processing; drop the task
      DBMS_PARALLEL_EXECUTE.DROP_TASK('mytask');
    
    END;
    /
    

    Oracle 文档:https://docs.oracle.com/database/121/ARPLS/d_parallel_ex.htm#ARPLS67333

    【讨论】:

    • 我认为这对于非常大且使用率很高的桌子来说是个好主意!我还没有这样的失败,但你有 +1 :)
    • 某事告诉我,这也避免了长时间锁定整个表。有了这个,只有一个“小”块将在单次运行期间被锁定。
    【解决方案5】:

    什么是数据库版本?查看 11g 中的虚拟列:

    添加具有默认值的列 http://www.oracle.com/technology/pub/articles/oracle-database-11g-top-features/11g-schemamanagement.html

    【讨论】:

      【解决方案6】:

      update Hotels set Discount=30 where Hotelid >= 1 and Hotelid

      【讨论】:

        【解决方案7】:

        对于 Postgresql,我会这样做(如果我们确定不再发生更新/插入):

        create table new_table as table orig_table with data;
        update new_table set column = <expr> 
        start transaction;
        drop table orig_table;
        rename new_table to orig_table;
        commit;
        

        【讨论】:

          猜你喜欢
          • 2012-01-23
          • 1970-01-01
          • 2020-02-19
          • 2012-07-10
          • 2011-03-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-10-01
          相关资源
          最近更新 更多