使用 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.id
和 final.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的主要内容,如果未能解决你的问题,请参考以下文章
如何从一个DataTable中复制数据行到另一个DataTable中
当 SQL Server 表中的列“createdDate”从现在起经过 90 天后,如何更新其具有数百万行的列?我们可以使用触发器吗?