Sql 更新x集y=null需要很长时间

Sql 更新x集y=null需要很长时间,sql,oracle,plsql,oracle11g,Sql,Oracle,Plsql,Oracle11g,在工作中,我有一个很大的表(大约300万行,比如40-50列)。我有时需要清空一些列并用新数据填充它们。我没想到的是 UPDATE table1 SET y = null 比用数据填充列要花费更多的时间,例如,在sql查询中从同一表的其他列生成的数据,或从子查询中的其他表查询的数据。无论是一次遍历所有表行(如上面的更新查询中),还是使用光标逐行遍历表(使用pk),都没有关系。无论我是在工作中使用大表,还是创建一个小测试表并用数百个测试行填充它,这都无关紧要。将列设置为null总是比使用一些动态

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

UPDATE table1 SET y = null
比用数据填充列要花费更多的时间,例如,在sql查询中从同一表的其他列生成的数据,或从子查询中的其他表查询的数据。无论是一次遍历所有表行(如上面的更新查询中),还是使用光标逐行遍历表(使用pk),都没有关系。无论我是在工作中使用大表,还是创建一个小测试表并用数百个测试行填充它,这都无关紧要。将列设置为null总是比使用一些动态数据(每行不同)更新列花费更长的时间(在整个测试中,我遇到了2到10的系数)

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

谢谢你的帮助

备注:我正在使用oracle 11g2,并使用plsql developer和oracle sql developer发现了这些结果。

这是因为它从数据块中删除了

delete
是最难的操作如果可以避免
删除
,那么就这样做。

我建议您使用该列创建另一个空表(
createtable as select
,例如,
insert select
),并用您的过程填充它(该列)。删除旧表,然后用当前名称重命名新表

更新:

另一件重要的事情是,您应该使用新值按原样更新列。将它们设置为null并在之后重新填充它们是无用的。 如果并非所有行都有值,则可以按如下方式执行更新:

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

并将源中不存在的行设置为null。

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

编辑:


显示问题的是Y列,还是独立于正在更新的列?您可以发布表定义,包括约束吗?

还有助于加快更新速度的方法是使用
更改表1 NOLOGING
,以便更新不会生成重做日志。另一种可能是删除列并重新添加它。由于这是一个DDL操作,它既不会生成重做也不会生成撤消。

摘要

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

空值有什么特别之处?

从:

“如果Null位于具有数据值的列之间,则会存储在数据库中。在这种情况下,需要1字节来存储列的长度(零)

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

测试

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

为简单起见,下面的脚本不会抛出高结果和低结果,只测试具有单个列的表。但无论列数、列数据以及更新的列数如何,问题仍然会出现

我使用中的RunStats实用程序来比较将值更新为值与将值更新为空的资源消耗

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;
/
解决方案?

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


然而,表压缩有很多潜在的缺点。更新到空值会运行得更快,但每更新一次都会运行得稍慢。

我会尝试Tom Kyte建议的大型更新。 当涉及到大型表时,最好是这样:取几行,更新它们,再取一些,更新那些,等等。不要试图对所有表进行更新。从一开始这就是一个致命的举动

基本上创建二进制整数索引表,一次获取10行,然后更新它们

下面是一段代码,我成功地使用了大型表。因为我很懒,现在是凌晨2点,我会把它复制粘贴到这里,让你弄明白,但是如果你需要帮助,请告诉我:

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;

你能发布你的执行/解释计划吗?如果我一次浏览整个表格,就没有where条款了。如果我逐行检查表,那么会有一个where子句引用表的主键。结果在两个版本中保持不变。至于执行计划,我将准备一个示例和一个分步示例,以在今天晚些时候重现结果。我想知道重建表是否更快?我的意思是像
createtablenewtab选择col1、col2、cast(null为某物)、oldtab中的col4这样的东西
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;