Теперь давайте рассмотрим вторую причину, по которой разработчики соблазняются фиксировать обновления в процедурном цикле, и которые происходят изза безуспешных попыток экономно использовать “ограниченный ресурс” (сегменты отката). Это вопрос конфигурации; вы должны обеспечить наличие достаточного пространства для сегментов отмены, чтобы правильно выставить размер ваших транзакций. Фиксация в цикле, помимо того, что работает медленнее, также часто вызывает появление ужасной ошибки ORA-01555. Рассмотрим это более детально.
Как вы знаете после прочтения глав 1 и 7, многоверсионная модель Oracle использует сегменты отмены для реконструирования блоков в том виде, в каком они были на момент запуска вашего оператора или транзакции (в зависимости от режима изоляции). Если необходимая информация отмены больше не существует, вы получаете сообщение об ошибке ORA-01555: snapshot too old (“ORA-1555: устаревший снимок”), и ваш запрос не выполняется. Поэтому, если вы модифицируете таблицу, которую читаете (как в предыдущем примере), то генерируете информацию отмены, необходимую вашему запросу. Оператор UPDATE генерирует информацию отмены, которую ваш запрос, вероятно, использует, чтобы получить согласованное по чтению представление данных, которые ему нужно обновить. Если вы выполняете фиксацию, то позволяете системе повторно задействовать пространство отмены, только что использованное вами. И если она повторно использует это пространство, уничтожая данные отмены, которые впоследствии понадобятся вашему запросу, у вас возникает серьезная проблема. Оператор SELECT завершается неудачей, а UPDATE останавливается на полпути. Вы получаете частично завершенную логическую транзакцию и, вероятно, не имеете никакой возможности перезапустить ее (подробнее об этом ниже).
Давайте рассмотрим сказанное на небольшом примере. В маленькой тестовой базе данных я создал таблицу:
ops$tkyte@ORA10G> create table t as select * from all_objects;
Table created.
Таблица создана.
ops$tkyte@ORA10G> create index t_idx on t(object_name);Index created.
Индекс создан.
ops$tkyte@ORA10G> exec dbms_stats.gather_table_stats( user, 'T', cascade=>true );
PL/SQL procedure successfully completed.
Процедура PL/SQL успешно завершена.
Затем я создал очень маленькое табличное пространство отмены и указал системе использовать его. Обратите внимание, что при установке AUTOEXTEND OFF я ограничил размер UNDO в системе до 2 Мбайт или менее:
ops$tkyte@ORA10G> create undo tablespace undo_small
2 datafile size 2m
3 autoextend off
4 /
Tablespace created.
Табличное пространство создано.
ops$tkyte@ORA10G> alter system set undo_tablespace = undo_small;
System altered.
Система изменена.
Теперь, имея только одно маленькое табличное пространство отмены, я запустил следующий блок кода для выполнения UPDATE:
ops$tkyte@ORA10G> begin
2 for x in ( select /*+ INDEX(t t_idx) */ rowid rid, object_name, rownum r
3 from t
4 where object_name > ' ' )
5 loop
6 update t
7 set object_name = lower(x.object_name)
8 where rowid = x.rid;
9 if ( mod(x.r,100) = 0 ) then
10 commit;
11 end if;
12 end loop;
13 commit;
14 end;
15 /
begin
*
ERROR at line 1:
ORA-01555: snapshot too old: rollback segment number with name "" too small
ORA-06512: at line 2
ОШИБКА в строке 1:
ORA-01555: устаревший снимок: номер сегмента отката с именем "" очень мал
ORA-06512: в строке 2
Возникла ошибка. Хочу отметить, что я добавил подсказку (hint) для использования индекса в запросе и конструкцию WHERE, чтобы гарантировать чтение таблицы в случайном порядке (вместе они заставляют стоимостной оптимизатор читать таблицу, “отсортированную” по ключу индекса). Когда мы обрабатываем таблицу через индекс, то читаем блок для извлечения одной строки, а следующая строка с высокой степенью вероятности должна уже быть в другом блоке. В конечном итоге обрабатываются все строки из блока 1, но не одновременно. Блок 1 может содержать, скажем, данные всех строк, у которых OBJECT_NAME начинается с букв A, M, N, Q и Z. Поэтому мы должны обратиться к этому блоку много раз, поскольку читаем данные, отсортированные по OBJECT_NAME, и можно предположить, что множество значений OBJECT_NAME будут начинаться с букв между A и M. Поскольку фиксация выполняется часто и пространство отката используется повторно, мы со временем повторно навещаем блок, в котором просто невозможно выполнить откат к точке во времени, когда запрос начался, и именно в этот момент получаем ошибку.
Это очень надуманный пример — просто чтобы наглядно продемонстрировать, что может случиться. Мой оператор UPDATE сгенерировал откат. У меня было очень маленькое табличное пространство отката (всего 2 Мбайт). Я многократно заворачивал сегменты отката, поскольку они используются в циклическом режиме. Каждый раз, когда я выполнял фиксацию, то тем самым позволял Oracle перезаписывать сгенерированные данные отката. В конце концов, мне понадобился некоторый кусочек данных, который я сгенерировал, но его не оказалось в сегменте отката, поэтому возникла ошибка ORA-01555.
Вы вправе указать, что в этом случае, если бы я не выполнял фиксацию в строке 10 предыдущего примера, то получил бы следующую ошибку:
begin
*
ERROR at line 1:
ORA-30036: unable to extend segment by 8 in undo tablespace 'UNDO_SMALL'
ORA-06512: at line 6
ОШИБКА в строке 1:
ORA-30036: невозможно расширить сегмент на 8 в табличном пространстве
отмены 'UNDO_SMALL'
ORA-06512: в строке 6
Ниже описаны главные отличия между этими двумя ошибками.
Пример с ORA-01555 оставляет мое обновление в совершенно неопределенном состоянии. Часть работы сделана, часть — нет.
Я абсолютно ничего не могу сделать, чтобы избежать ORA-01555, если стану выполнять фиксацию в цикле курсора FOR.
Ошибки ORA-30036 можно избежать, выделив соответствующие ресурсы в системе. Эта ошибка устраняется корректным определением размера сегмента отката; первая ошибка — нет. Затем, даже если я не смогу избежать этой ошибки, то по крайней мере обновление откатывается и база данных остается в известном, согласованном состоянии — я не оставляю ее в состоянии частичного обновления.
Главным недостатком здесь является то, что вы не можете “сэкономить” пространство отката за счет частой фиксации — оно вам нужно. Я работал в однопользовательской системе, когда получил ошибку ORA-01555. Достаточно было одного сеанса, чтобы привести к этой ошибке, а в реальной жизни таких ошибок будет множество, если каждый индивидуальный сеанс вызовет свою собственную ошибку ORA-01555. Разработчики и администраторы баз данных должны работать совместно, чтобы адекватно определить размер этих сегментов для задач, решаемых в системе. Здесь не должно быть кратковременных изменений. Путем тщательного анализа вашей системы вы должны выяснить размер наибольшей транзакции и соответствующим образом установить размеры сегментов отката. Динамическое представление производительности V$UNDOSTAT может очень пригодиться для слежения за генерируемыми откатами и продолжительностью самых длительных запросов. Многие люди склонны воспринимать вещи вроде временной области, области отмены и области повторения как “накладные расходы”, которые следует сокращать насколько возможно. Это напоминает о проблеме, с которой столкнулась компьютерная индустрия 1 января 2000 года — она была вызвана стремлением сэ
кономить 2 байта в полях дат. Эти компоненты базы данных не являются накладными расходами, а ключевыми компонентами системы. Их размер должен быть определен должным образом (не слишком большим, но и не слишком маленьким).
< Предыдущая | Следующая > |
---|