update x set y = null需要很长时间

时间:2011-11-10 11:48:04

标签: sql oracle plsql oracle11g

在工作中,我有一张大桌子(大约300万行,如40-50列)。我有时需要清空一些列并用新数据填充它们。我没想到的是那个

UPDATE table1 SET y = null

比使用数据填充列要花费更多的时间,例如,在同一个表的其他列的sql查询中生成的数据,或从子查询中的其他表查询的数据。如果我一次遍历所有表行(如上面的更新查询中)或者如果我使用游标逐行遍历表(使用pk),则无关紧要。如果我在工作中使用大表,或者如果我创建一个小测试表并用数十万个测试行填充它并不重要。将列设置为null总是需要更长的时间(在整个测试中,我遇到了2到10的因子),而不是使用一些动态数据更新列(每行都不同)。

这是什么原因?将列设置为null时,Oracle会做什么?或者 - 我在推理中的错误是什么?

感谢您的帮助!

P.S。:我正在使用oracle 11g2,并使用plsql developer和oracle sql developer找到了这些结果。

5 个答案:

答案 0 :(得分:6)

Y列是否已编入索引?可能是将列设置为null意味着Oracle必须从索引中删除,而不是仅仅更新它。如果是这种情况,您可以在更新数据后删除并重建它。

编辑:

只是Y列显示问题,还是独立于要更新的​​列?你可以发表表定义,包括约束吗?

答案 1 :(得分:4)

<强>摘要

我认为更新为null的速度较慢,因为Oracle(错误地)尝试利用它存储空值的方式,导致它经常重新组织块中的行(“堆块压缩”),从而创建了大量的额外的UNDO和REDO。

null有什么特别之处?

来自Oracle Database Concepts

“如果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 selectinsert 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操作,它既不会重做也不会撤消。