摘要
我想更新到空变慢了,因为甲骨文(错误地)试图利用它的存储空值的方式的优势,使其在块经常重新组织行(“堆块压缩“),创造了很多额外的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会运行得更快,但其他更新至少会稍微慢一点。
您可以发布您执行/解释计划? – diagonalbatman
是否有'where'子句? – Johan
如果我一次通过整个表,没有where子句。如果我逐行浏览表,那么有一个引用表的主键的where子句。结果在两个版本中保持不变。至于执行计划,我将准备一个和一步一步的例子来今天晚些时候再现结果。 –