Во что обходится обмен секций?
Давайте начнем с действительно простого случая- обмена таблицы из 1000000 строк с пустой секцией. Давайте начнем с SQL-операторов для создания секционированной таблицы и не секционированной таблицы - мы будем повторно использовать этот код, с некоторыми изменениями, в оставшейся части статьи. Нам также понадобится табличное пространство, в котором будет около 200 Мбайт свободного места.
create table parent ( id number(12,6), v1 varchar2(10), padding varchar2(100) ) partition by range(id) ( partition p1000 values less than (1000), partition p3000 values less than (3000), partition p5000 values less than (5000) );
create table parent_ex ( id number(12,6), v1 varchar2(10), padding varchar2(100)) nologging -- чтобы сэкономить немного времени ;
insert /*+ append ordered full(s1) use_nl(s2) */ into parent_ex select 3000 + trunc((rownum-1)/500,6), to_char(rownum), rpad('x',100) from sys.source$ s1, -- необходима соответствующая привилегия sys.source$ s2 where rownum <= 1000000;
А теперь давайте обменяем таблицу с секцией P5000, которой и принадлежат эти данные. Но давайте включим timing, чтобы увидеть, сколько времени на это потребуется.
alter table parent exchange partition p5000 with table parent_ex;
Elapsed: 00:00:17.06
Что произошло с "очень быстрым" переносом? Повторите тест с включенным sql_trace и вы обнаружите в трассировочном файле следующий SQL-оператор. Сервер Oracle проверяет, есть ли в таблице parent_ex строки, не принадлежащие указанной секции таблицы parent.
select 1 from "PARENT_EX" where TBL$OR$IDX$PART$NUM("PARENT",0,0,65535,"ID") != :1
Для этого необходимо выполнить полный просмотр таблицы и вызывать функцию для каждой строки в загружаемой таблице - представьте себе результат в реальной системе с большими объемами данных и загруженной подсистемой ввода-вывода.
Но не бойтесь, - именно для таких случаев предназначена конструкция without validation. Повторите эксперимент, но поместите в конце команды exchange эту конструкцию.
alter table parent exchange partition p5000 with table parent_ex without validation;
Elapsed: 00:00:00.00
Ур-ра - так работает намного быстрее! Но, не торопитесь с выводами; в реальных базах данных обычно есть индексы и ограничения первичного или уникального ключа. Поэтому давайте повторим упражнение, но добавим ограничение первичного ключа к основной и обмениваемой таблице перед обменом (Обратите внимание на синтаксис версии 9 для полного указания индекса при добавлении ограничения - я решил обеспечить выполнение ограничения уникального/первичного ключа с помощью уникального индекса). В этом случае, мы, вероятно, захотим включить в процесс обмена и индексы, чтобы данные оказались в основной таблице с пригодной к использованию секцией индекса.
alter table parent add constraint p_pk primary key(id) using index (create index p_pk on parent(id) nologging local);
alter table parent_ex add constraint px_pk primary key(id) using index (create index px_pk on parent_ex(id) nologging);
alter table parent exchange partition p5000 with table parent_ex including indexes without validation;
Elapsed: 00:00:28.05
А теперь что произошло? Повтор (не такой уж быстрый) эксперимента с включенным
sql_trace выдает представленный ниже SQL-оператор. Сервер Oracle проверяет, что обмен не приведет к проблеме с уникальностью. Запрос просматривает всю таблицу
parent (исключая секцию, которую мы обмениваем), чтобы узнать, нет ли дубликатов загружаемых нами строк. Это собенно глупо, поскольку ограничение уникальности поддерживается с помощью локального индекса, поэтому он обязательно включает ключ секционирования, а это означает, что строка может быть только в одной секции, и мы уже пообещали (с помощью конструкции
without validation), что все строки принадлежат той секции, в которую мы их помещаем. Конечно, верно, что в других секциях могут быть строки, которые должны бы находиться в загружаемой секции, но и в этом случае я не думаю, что хочу тратить время на их поиск прямо сейчас.
Проверка значений на определенность также кажется немного странной, поскольку в данном случае у нас есть ограничение первичного ключа, которое неявно предполагает ограничение
not null. Можно предположить, что это сделано, чтобы использовать тот же код не только для ограничений первичного ключа, но и для ограничений уникальности.
Обратите внимание, в частности, на операторы
minus и
intersect
- они требуют сортировки всего результирующего множества, и я ещё легко отделался со своими 28 секундами, поскольку (a) у меня не было данных в исходной таблице
parent, и (b) данные в таблице
parent_ex уже были отсортированы.
select "ID" from "TEST_USER"."PARENT_EX" where not( "ID" is null) intersect select "ID" from ( select "ID" from "TEST_USER"."PARENT" minus select "ID" from "TEST_USER"."PARENT" partition (P5000) ) B where not( "ID" is null)
Можно ли обойти эти огромные затраты? Да, и все, что нужно сделать - перевести ограничения по обеим таблицам в состяние
novalidate перед выполнением обмена.
alter table parent_ex modify primary key novalidate; alter table parent modify primary key novalidate;
Общее назначение опции
novalidate для ограничени - сообщить серверу Oracle, что он не должен проверять уже существующие данные, чтобы убедиться, что они соответствуют ограничению. Новые данные или изменения, которые вы будете делать в старых данных, будут, однако, проверяться, поскольку ограничение все равно действует.
Но тут есть очень тонкая скрытая ловушка. Существует специальный алгоритм оптимизатора, который оценивает стоимость ограничений первичного и уникального ключа ниже обычного,
если только ограничение не допускает отложенную проверку или (как мы только что его установили) не находится в состоянии
novalidate. Если мы изменим состояние ограничений с
validate на
novalidate, чтобы снизить затраты на обмен секций, мы можем обнаружить, что при этом случайный запрос начал выполняться иначе!
Так что, пожалуй, лучше оставить ограничения в состоянии
validate, и не гнаться за "дешевизной", просто установив опцию
without validation
при обмене. Один проход по добавляемым данным может быть куда более приемлемым, чем альтернативные варианты.
Содержание раздела