Ответ 1
Резюме
Я думаю, что обновление до нуля медленнее, потому что Oracle (неправильно) пытается воспользоваться тем, как он хранит значения NULL, заставляя его часто реорганизовывать строки в блоке ( "сжатие кучи" ), создавая много дополнительные UNDO и REDO.
Что такого особенного в null?
Из Концепции базы данных Oracle:
"Нули хранятся в базе данных, если они попадают между столбцами со значениями данных. В этих случаях они требуют 1 байт для хранения длины столбца (ноль).
Конечные нули в строке не требуют хранения, потому что заголовок новой строки сигнализирует, что остальные столбцы в предыдущей строке являются нулевыми. Например, если последние три столбца таблицы являются нулевыми, для этих столбцов не сохраняется информация. В таблицах со многими столбцами, столбцы с большей вероятностью содержат нули, должны быть определены последними для сохранения дискового пространства. "
Test
Обновление бенчмаркинга очень сложно, потому что истинную стоимость обновления невозможно измерить только из инструкции обновления. Например, log-переключатели будут не происходит с каждым обновлением, и отсроченная очистка блока произойдет позже. Чтобы точно протестировать обновление, необходимо выполнить несколько запусков, объекты должны быть воссозданы для каждого прогона, а высокие и низкие значения должны быть отброшены.
Для простоты script ниже не выбрасывает высокие и низкие результаты и только проверяет таблицу с одним столбцом. Но проблема по-прежнему встречается независимо от количества столбцов, их данных и столбца.
Я использовал утилиту RunStats из http://www.oracle-developer.net/utilities.php, чтобы сравнить потребление ресурсов от обновления до значения с обновлением до -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, я думаю, он фактически ничего не делает. Повтор, отмена и прошедшее время между двумя запусками почти идентичны с включенным сжатием таблицы.
Тем не менее, есть много потенциальных недостатков для сжатия таблицы. Обновление до нулевого значения будет выполняться намного быстрее, но каждое другое обновление будет работать хотя бы немного медленнее.