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

Tho*_*ich 15 sql oracle plsql oracle11g

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

UPDATE table1 SET y = null
Run Code Online (Sandbox Code Playgroud)

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

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

谢谢你的帮助!

PS:我使用的是oracle 11g2,并使用plsql developer和oracle sql developer找到了这些结果.

DCo*_*kie 6

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

编辑:

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


Jon*_*ler 4

概括

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

null 有什么特别之处?

来自Oracle 数据库概念

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

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

测试

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

为简单起见,下面的脚本不会抛出高结果和低结果,而仅测试具有单列的表。但无论列数、数据以及更新哪一列,问题仍然会发生。

我使用http://www.oracle-developer.net/utilities.php中的 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;
/
Run Code Online (Sandbox Code Playgroud)

结果

有很多差异,我认为最相关的是以下四个:

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
Run Code Online (Sandbox Code Playgroud)

解决方案?

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

然而,表压缩有很多潜在的缺点。更新到 null 会运行得更快,但所有其他更新都会运行得至少稍微慢一些。