2011-11-10 40 views
15

在工作中,我有一个大表(大约300万行,如40-50列)。我有时需要清空一些列并填充新数据。我没想到的是,update x set y = null需要很长时间

UPDATE table1 SET y = null 

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

这是什么原因? Oracle在将列设置为空时做了什么?或者 - 我在推理方面的错误是什么?

感谢您的帮助!

P.S .:我使用oracle 11g2,并且使用plsql developer和oracle sql developer发现了这些结果。

+0

您可以发布您执行/解释计划? – diagonalbatman

+0

是否有'where'子句? – Johan

+0

如果我一次通过整个表,没有where子句。如果我逐行浏览表,那么有一个引用表的主键的where子句。结果在两个版本中保持不变。至于执行计划,我将准备一个和一步一步的例子来今天晚些时候再现结果。 –

回答

4

摘要

我想更新到空变慢了,因为甲骨文(错误地)试图利用它的存储空值的方式的优势,使其在块经常重新组织行(“堆块压缩“),创造了很多额外的UNDO和REDO。

null有什么特别之处?

Oracle Database Concepts

“空值被存储在数据库中,如果它们落在与数据值的列之间。在它们需要1个字节来存储列(零)的长度这些情况下

行中的尾随零点不需要存储,因为新的行标题指示前一行中的其余列为空。例如,如果表的最后三列为空,则不会为这些列存储信息。有很多列, 应该定义最可能包含空值的列以节省磁盘空间。“

测试

标杆更新是非常困难的,因为更新的真实成本,不能只是从更新语句来测量。例如,每次更新都不会发生日志开关 ,并且延迟块清除将在稍后发生。要准确地测试更新,应该有多次运行,每次运行都应该重新创建对象 ,并且应丢弃高和低值。

为简单起见,下面的脚本不会丢弃高和低的结果,而只会测试带有单列的表。但问题依然存在,无论列的数量,数据以及更新哪个列。

我使用http://www.oracle-developer.net/utilities.php的RunStats实用程序来比较update-to-a-value与update-null-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 

解决方案?

我能想到的唯一可能的解决方案是启用表压缩。压缩表不会出现拖尾空存储技巧。 因此,即使“堆块压缩”数字在Run2中从2028到23208变得更高,我想它实际上并没有做任何事情。 两次运行之间的重做,撤消和已用时间几乎与启用的表压缩相同。

但是,表压缩有很多潜在的缺点。更新为null会运行得更快,但其他更新至少会稍微慢一点。

1

这是因为它从删除从块数据。

delete是最难的操作。 如果你可以避免delete,那就去做吧。

我建议您创建另一个表,该列为null(例如,Create table as selectinsert select),然后用您的过程填充它(列)。删除旧表格,然后用当前名称重命名新表格。

UPDATE:

另一个重要的事情是,你应该就是更新列,用新值。将它们设置为空并在其之后重新填充它们是没有用的。 如果你没有值的所有行,你可以做更新这样的:

udpate table1 
set y = (select new_value from source where source.key = table1.key) 

,并会设置为null不会在源存在的行。

6

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

编辑:

是它表现出的问题,或者是独立柱的只是第Y列被更新?你能发布表格定义,包括约束吗?

+0

+1 - 我认为这是问题。 –

+0

对不起,忘了说,该列没有索引。 –

-3

什么也可以帮助加快更新是使用alter table table1 nologging,以便更新不会生成重做日志。另一种可能性是删除列并重新添加它。由于这是一个DDL操作,它将不会生成重做或撤消。

+0

删除列将做同样的事情。从块中删除数据。在性能方面是相同的 - 持续时间相同。 –

+0

真正的弗洛林,我忽略了这一点。但是'nologging'提示仍然有效。感谢您的纠正! –

+0

是的,nologging会加快查询速度。 –

-1

我会尝试Tom Kyte在大型更新中提出的建议。 当谈到巨大的表格时,最好这样做:取几行,更新它们,再多做一些,更新它们等等。不要尝试在所有表格上发布更新。这从一开始就是一个杀手级的举动。

基本上创建binary_integer索引表,一次获取10行,并更新它们。

这是一段代码,我使用了大表成功。由于IM懒惰,它像现在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 [email protected] t, 
      [email protected] 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; 
+1

我强烈反对用许多小SQL语句替换一个大的SQL语句会更好。 (尽管在多用户环境中,您可能偶尔需要为并发执行此操作,或者由于像小型UNDO表空间这样的资源有限)。多个小型SQL语句需要时间在SQL和PL/SQL之间切换。单个UPDATE可能需要更少的UNDO,即多个UPDATE。 (FORALL而不是FOR将有助于上下文切换,但似乎完全不会减少UNDO的大小。) –