更新表中所有行的有效方法
Posted
技术标签:
【中文标题】更新表中所有行的有效方法【英文标题】:Efficient way to update all rows in a table 【发布时间】:2011-02-07 19:20:22 【问题描述】:我有一张包含很多记录的表(可能超过 500 000 或 1 000 000)。我在此表中添加了一个新列,我需要使用此表中另一列的相应行值为该列中的每一行填充一个值。
我尝试使用单独的事务来选择每下一个 100 条记录块并更新它们的值,但是更新 Oracle10 中的所有记录仍然需要数小时。
在不使用某些特定方言的功能的情况下,在 SQL 中执行此操作的最有效方法是什么,因此它适用于任何地方(Oracle、MSSQL、mysql、PostGre 等)?
附加信息:没有计算字段。有索引。使用生成的 SQL 语句逐行更新表。
【问题讨论】:
如果INDEX
es 出现在更新/插入的列中,很少有人会走到极端,让夜间工作对其进行分析。
我们需要更多信息。告诉我们有关表架构的信息......任何“计算”列?有什么索引吗? 500k - 1m 行无论如何都不是很多记录。
感谢大家的快速响应。我跳过了我使用生成的 SQL 语句的部分。现在我深入研究了它,它看起来像生成的 SQL 逐行更新!因此,任何将 100 条记录分开的尝试都是毫无意义的……我将更改代码以生成正确的 SQL UPDATE 语句,如已接受的答案所示。
【参考方案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;
【讨论】:
以上是关于更新表中所有行的有效方法的主要内容,如果未能解决你的问题,请参考以下文章