在工作中,我有一张大桌子(大约300万行,如40-50列)。我有时需要清空一些列并用新数据填充它们。我没想到的是那个
UPDATE table1 SET y = null
比使用数据填充列要花费更多的时间,例如,在同一个表的其他列的sql查询中生成的数据,或从子查询中的其他表查询的数据。如果我一次遍历所有表行(如上面的更新查询中)或者如果我使用游标逐行遍历表(使用pk),则无关紧要。如果我在工作中使用大表,或者如果我创建一个小测试表并用数十万个测试行填充它并不重要。将列设置为null总是需要更长的时间(在整个测试中,我遇到了2到10的因子),而不是使用一些动态数据更新列(每行都不同)。
这是什么原因?将列设置为null时,Oracle会做什么?或者 - 我在推理中的错误是什么?
感谢您的帮助!
P.S。:我正在使用oracle 11g2,并使用plsql developer和oracle sql developer找到了这些结果。
答案 0 :(得分:6)
Y列是否已编入索引?可能是将列设置为null意味着Oracle必须从索引中删除,而不是仅仅更新它。如果是这种情况,您可以在更新数据后删除并重建它。
编辑:
只是Y列显示问题,还是独立于要更新的列?你可以发表表定义,包括约束吗?
答案 1 :(得分:4)
<强>摘要强>
我认为更新为null的速度较慢,因为Oracle(错误地)尝试利用它存储空值的方式,导致它经常重新组织块中的行(“堆块压缩”),从而创建了大量的额外的UNDO和REDO。
null有什么特别之处?
“如果Null位于具有数据值的列之间,则存储在数据库中。在这些情况下,它们需要1个字节来存储列的长度(零)。
一行中的尾随空值不需要存储,因为新的行标头表示前一行中的其余列为空。例如,如果表的最后三列为空,则不会为这些列存储任何信息。在包含许多列的表中 应该最后定义更可能包含空值的列以节省磁盘空间。“
<强>测试强>
基准测试更新非常困难,因为更新的真实成本无法仅通过更新语句来衡量。例如,日志切换器将 每次更新都不会发生,延迟块清除将在以后发生。要准确测试更新,应该有多次运行, 应该为每次运行重新创建对象,并且应该丢弃高值和低值。
为简单起见,下面的脚本不会抛出高低结果,只测试具有单列的表。但是,无论列数,数据以及更新的列,问题仍然存在。
我使用http://www.oracle-developer.net/utilities.php中的RunStats实用程序来比较更新到值的资源消耗和更新到null。
create table test1(col1 number);
BEGIN
dbms_output.enable(1000000);
runstats_pkg.rs_start;
for i in 1 .. 10 loop
execute immediate 'drop table test1 purge';
execute immediate 'create table test1 (col1 number)';
execute immediate 'insert /*+ append */ into test1 select 1 col1
from dual connect by level <= 100000';
commit;
execute immediate 'update test1 set col1 = 1';
commit;
end loop;
runstats_pkg.rs_pause;
runstats_pkg.rs_resume;
for i in 1 .. 10 loop
execute immediate 'drop table test1 purge';
execute immediate 'create table test1 (col1 number)';
execute immediate 'insert /*+ append */ into test1 select 1 col1
from dual connect by level <= 100000';
commit;
execute immediate 'update test1 set col1 = null';
commit;
end loop;
runstats_pkg.rs_stop();
END;
/
<强>结果强>
存在许多差异,这些是我认为最相关的四个:
Type Name Run1 Run2 Diff
----- ---------------------------- ------------ ------------ ------------
TIMER elapsed time (hsecs) 1,269 4,738 3,469
STAT heap block compress 1 2,028 2,027
STAT undo change vector size 55,855,008 181,387,456 125,532,448
STAT redo size 133,260,596 581,641,084 448,380,488
<强>方案吗
我能想到的唯一可行解决方案是启用表压缩。压缩表不会发生trailing-null存储技巧。 因此,即使Run2的“堆块压缩”数量更高,从2028到23208,我猜它实际上并没有做任何事情。 两次运行之间的重做,撤消和已用时间几乎与启用表压缩相同。
但是,表压缩有很多潜在的缺点。更新为null将运行得更快,但每个其他更新的运行速度至少会稍慢。
答案 2 :(得分:1)
那是因为 从数据块中删除 。
delete
是最难的操作。 如果您可以避免使用delete
,请执行此操作。
我建议您创建另一个表,其中该列为null(例如Create table as select
或insert select
),并使用您的过程填充它(列)。删除旧表,然后使用当前名称重命名新表。
<强>更新强>
另一个重要的事情是您应该使用新值更新列。将它们设置为null并在之后重新填充它们是没用的。 如果您没有所有行的值,则可以执行以下更新:
udpate table1
set y = (select new_value from source where source.key = table1.key)
并将设置为null在源中不存在的那些行。
答案 3 :(得分:-1)
我会尝试Tom Kyte在大量更新时提出的建议。 当涉及到巨大的表时,最好是这样:取几行,更新它们,多取一些,更新那些等。不要试图在所有表上发布更新。这从一开始就是一个杀手锏。
基本上创建binary_integer索引表,一次获取10行,然后更新它们。
这是我用过大表并成功的一段代码。因为我很懒,它就像2AM现在生病只是复制粘贴在这里,让你搞清楚,但如果你需要帮助,请告诉我:
DECLARE
TYPE BookingRecord IS RECORD (
bprice number,
bevent_id number,
book_id number
);
TYPE array is TABLE of BookingRecord index by binary_integer;
l_data array;
CURSOR c1 is
SELECT LVC_USD_PRICE_V2(ev.activity_version_id,ev.course_start_date,t.local_update_date,ev.currency,nvl(t.delegate_country,ev.sponsor_org_country),ev.price,ev.currency,t.ota_status,ev.location_type) x,
ev.title,
t.ota_booking_id
FROM ota_gsi_delegate_bookings_t@diseulprod t,
inted_parted_events_t@diseulprod ev
WHERE t.event_id = ev.event_id
and t.ota_booking_id =
BEGIN
open c1;
loop
fetch c1 bulk collect into l_data limit 20;
for i in 1..l_data.count
loop
update ou_inc_int_t_01
set price = l_data(i).bprice,
updated = 'Y'
where booking_id = l_data(i).book_id;
end loop;
exit when c1%notfound;
end loop;
close c1;
END;
答案 4 :(得分:-3)
什么也可以帮助加快更新是使用alter table table1 nologging
,以便更新不会生成重做日志。另一种可能性是删除列并重新添加它。因为这是一个DDL操作,它既不会重做也不会撤消。