WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     | 1 |   ...   | 3 | 4 || 6 | 7 |   ...   | 24 |

«Oracle для профессионалов Том Кайт DiaSoft 2003 торгово-издательский дом Москва • Санкт-Петербург • Киев УДК 681.3. 06(075) Б Б К 32.973.2 К 91 КАЙТ ТОМ К 91 Oracle для профессионалов. Пер. с ...»

-- [ Страница 5 ] --

Как видите, фиксация каждой строки заметно увеличила объем генерируемых данных повторного выполнения (итоговые результаты представлены в следующей таблице). Есть и другие причины, связанные с производительностью, объясняющие, почему не стоит фиксировать каждую измененную строку (или группу строк). Мы видели непосредственное доказательство этого в примере выше, где фиксация каждой строки требует в три раза больше времени, чем фиксация после завершения транзакции. Дополнительные затраты времени связаны с обращением к ядру сервера, установкой дополнительных защелок и возникновением конфликтов при доступе к общим ресурсам при выполнении каждого оператора. Итак, если что-то можно сделать с помощью одного оператора SQL — делайте это именно так. Кроме того, фиксируйте транзакцию, только когда она закончена, не раньше. Операция Количество затронутых строк Общий объем данных повторного выполнения (без фиксации) 442784 849600 469152 Общий объем % увеличения данных повторного выполнения (с фиксацией) 530396 956660 537132 20% 13% 14% Вставка 200 строк Изменение 200 строк Удаление 200 строк 200 200 Глава Представленный выше метод пригодится и для оценки побочных эффектов различных вариантов работы. Один из часто возникающих вопросов связан с триггерами. Помимо того, что в триггере BEFORE можно изменять указанные значения строки, есть ли еще какие-то особенности применения триггеров? Как оказалось, есть. Триггер BEFORE создает дополнительные данные повторного выполнения, даже не изменив ни одного значения в строке. Этот вопрос достоин отдельного изучения, и с помощью представленных выше методов можно убедиться, что: • триггер BEFORE или AFTER не влияет на объем данных повторного выполнения для операторов DELETE;

• оператор INSERT генерирует дополнительно одинаковый объем данных повторного выполнения при наличии триггера BEFORE или AFTER;

• на объем данных повторного выполнения, генерируемых оператором UPDATE, влияет только наличие триггера BEFORE — триггер AFTER не генерирует дополнительных данных;

• размер строки влияет на объем дополнительно генерируемых данных повторного выполнения для операторов INSERT, но не для операторов UPDATE. Используем таблицу Т из предыдущего примера: create table t (x int, у char(N), z date);

но создадим несколько ее версий с разными значениями N. В данном случае мы используем значения N = 30, 100, 500, 1000 и 2000 для получения строк разного размера. Я использовал простую таблицу для регистрации результатов нескольких экспериментов:

create table log (what varchar2(15), — — op varchar2(10), — — rowsize int, — redo_size int, — — rowcnt int);

— — будет иметь значения по trigger, after или before будет иметь значения insert, update или delete будет содержать размер столбца Y будет содержать объем сгенерированных данных повторного выполнения будет содержать количество обработанных строк Выполнив тест с различными размерами столбца Y, проанализируем результаты. Я использую следующую хранимую процедуру для генерации транзакций и записи объема сгенерированных данных повторного выполнения. Подпроцедура REPORT — это локальная процедура (видимая только в процедуре DO_WORK), выдающая на экран информацию о происходящем и записывающая полученные результаты в таблицу LOG. Основное тело процедуры выполняет проверяемые транзакции. Все начинается с запоминания текущего объема данных повторного выполнения в текущем сеансе, выполнения определенных действий, а затем генерируется отчет:

tkyte@TKYTE816> create or replace procedure do_work (p_what in varchar2) 2 as 3 l_redo_size number;

4 1 cnt number := 200;

Повторное выполнение и откат 5 6 procedure report(l_op in varchar2) 7 is 8 begin 9 select value-l_redo_size into l_redo_size from redo_size;

10 dbms_output.put_line(l_op || ' redo size = ' || l_redo_size || 11 ' rows' = ' || l_cnt || ' ' || 12 to_char(l_redo_size/l_cnt,"99,999.9') || 13 ' bytes/row');

14 insert into log 15 select p_what, l_op, data_length, l_redo_size, l_cnt 16 from user_tab_columns 17 where table_name = 'T' 18 and column_name = 'У';

19 end;

20 begin 21 select value into l_redo_size from redo_size;

22 insert into t 23 select object_id, object_name, created 24 from all_objects 25 where rownum <= l_cnt;

26 l_cnt := sql%rowcount;

27 commit;

28 report('insert');

29 30 select value into l_redo_size from redo_size;

31 update t set y = l o w e r ( y ) ;

32 l _ c n t := s q l % r o w c o u n t ;

33 commit;

34 report('update');

35 36 select value into l_redo_size from redo_size;

37 delete from t;

38 l_cnt := sql%rowcount;

39 commit;

40 report('delete');

41 end;

42 / Procedure created.

Теперь, когда все готово, я удалю и создам таблицу Т, изменяя размер столбца Y. Затем выполню следующий код для тестирования различных сценариев (без триггеров, с триггером BEFORE и с триггером AFTER):

tkyte@TKYTE816> truncate table t;

Table truncated. tkyte@TKYTE816> exec do_work('no trigger');

insert redo size = 443280 rows = 200 2,216.4 bytes/row update redo size = 853968 rows = 200 4,269.8 bytes/row delete redo size = 473620 rows - 200 2,368.1 bytes/row Глава PL/SQL procedure successfully completed. tkyte@TKYTE816> create or replace trigger before_insert_update_delete 2 before insert or update or delete on T for each row 3 begin 4 null;

5 end;

6 / Trigger created. tkyte@TKYTE816> truncate table t;

Table truncated. tkyte@TKYTE816> exec do_work('before trigger');

insert redo size = 465640 rows = 200 2,328.2 bytes/row update redo size = 891628 rows = 200 4,458.1 bytes/row delete redo size = 473520 rows = 200 2,367.6 bytes/row PL/SQL procedure successfully completed. tkyte@TKYTE816> drop trigger before_insert_update_delete;

Trigger dropped. tkyte@TKYTE816> create or replace trigger after_insert_update_delete 2 3 4 5 6 7 after insert or update or delete on T for each row begin null;

end;

/ Trigger created. tkyte@TKYTE816> truncate table t;

Table truncated. tkyte@TKYTE816> exec do_work('after trigger');

insert redo size = 465600 rows = 200 update redo size = 854028 rows = 200 delete redo size = 473580 rows = 200 PL/SQL procedure successfully completed. 2,328.0 bytes/row 4,270.1 bytes/row 2,367.9 bytes/row В ы ш е представлен результат теста, в котором столбец Y имел размер 2000 байт. После завершения всех тестов я получил следующие результаты запроса к регистрационной таблице:

tkyte@TKYTE816> break on op skip 1 tkyte@TKYTE816> set numformat 999, Повторное выполнение и откат tkyte@TKYTE816> select op, rowsize, no_trig, before_trig-no_trig, after_trig-no_trig 2 from ( select op, rowsize, 3 sum(decode(what, 'no trigger', redo_size/rowcnt,0)) no_trig, 4 sum (decode(what, 'before trigger', redo_size/rowcnt, 0)) before_trig, 5 sum(decode(what, 'after trigger', redo_size/rowcnt, 0)) after_trig 6 from log 7 group by op, rowsize 8 ) 9 order by op, rowsize 10 / OP delete ROWSIZE 30 100 500 1,000 2,000 30 100 500 1,000 2,000 30 100 500 1,000 2,000 NO_TRIG 272 344 765 1,293 2,368 60 136 574 1,113 2,216 294 431 1,238 2,246 4,270 BEFORE_TRIG-NO_TRIG AFTER_TRIG-NO_TRIG 0 -0 -0 -1 -1 213 208 184 162 112 189 188 188 188 188 0 -0 -0 -0 -0 213 208 184 162 112 0 0 -0 -0 insert update 15 rows selected. Если вам непонятен этот запрос или способ группировки результирующего множества, обратитесь к главе 12, посвященной аналитическим функциям, в которой я детально описываю способы группировки результирующего множества.

Внутренний запрос сгенерировал результирующее множество, содержащее средний объем в байтах данных повторного выполнения для строки для каждого из трех тестов. Внешний запрос просто выдает среднее количество байтов для строки при отсутствии триггера, а последующие два столбца показывают отличие других случаев от тестов при отсутствии триггера. Итак, для тестов с операторами DELETE заметного различия в объеме генерируемых для каждой строки данных повторного выполнения нет. Для операторов INSERT, однако, можно обнаружить дополнительные расходы ресурсов — от 213 до 112 байтов на строку, независимо от типа использованного триггера (BEFORE или Глава AFTER). Чем больше строка, тем меньше почему-то эти дополнительные расходы (я не разбирался, почему это так, но именно так и получается). Наконец, для операторов UPDATE обнаруживается два факта. Во-первых, объем генерируемых данных повторного выполнения не зависит от размера строки — дополнительные расходы на выполнение операторов UPDATE постоянны. Во-вторых, триггер AFTER оказывается намного эффективнее для операторов UPDATE, поскольку он вообще не влияет на генерацию данных повторного выполнения. Поэтому можно использовать эмпирическое правило: для операторов UPDATE по возможности используйте триггеры AFTER. Триггер BEFORE имеет смысл использовать, только если требуются его специфические функциональные возможности. Итак, теперь вы знаете, как оценить объем данных повторного выполнения, что необходимо делать всем разработчикам. Для этого требуется: • оценить размер транзакции (сколько данных изменяется);

• добавить от 10 до 20 процентов дополнительного пространства, в зависимости от количества изменяемых строк (чем больше строк, тем меньше процент дополнительных расходов);

• удвоить полученное значение для операторов UPDATE. В большинстве случаев это дает вполне приемлемые результаты для оценки. Удвоение объема для операторов UPDATE — это упрощение;

реальный объем зависит от способа изменения данных. Удвоение предполагает, что берется строка размером X байтов, и в результате изменения получится строка тоже размером X байтов. Если увеличивается маленькая строка, значение не удваивается (результат ближе к тому, который получается при выполнении оператора INSERT). Если уменьшается большая строка, значение тоже не удваивается (результат сходен с тем, который получается при выполнении оператора DELETE). Удвоение — "худший случай", поскольку некоторые опции и средства влияют на объем генерируемых данных, например наличие (или отсутствие, как в моем случае) индексов. Объем работы, необходимой для поддержки индекса, для разных операторов UPDATE может отличаться. Побочные эффекты триггеров также необходимо учесть (помимо фиксированных затрат ресурсов, описанных выше). Некоторые операции, выполняемые от имени сеанса неявно, например при установке опции ON DELETE CASCADE, также необходимо учитывать. Это поможет оценить объем данных повторного выполнения для оценки требований к пространству/производительности. Только тестирование в реальных условиях даст гарантированный результат. На примере представленного выше сценария вы сможете понять, как определить этот объем для своих объектов и транзакций.

Можно ли отключить генерацию записей в журнал повторного выполнения?

Этот вопрос задают очень часто. Простой и короткий ответ — "нет". Журнализация данных повторного выполнения принципиально важна для базы данных и расходы на нее нельзя считать потерей ресурсов. Журнализация действительно необходима, как бы Повторное выполнение и откат вы к ней не относились. Это факт, именно так работает сервер. Однако есть ряд операций, которые иногда можно выполнять, не генерируя данных в журнал повторного выполнения. Некоторые SQL-операторы поддерживают конструкцию NOLOGGING. Это не означает, что все операции с объектом будут выполняться без генерирования данных в журнал повторного выполнения. Просто некоторые специфические операции будут генерировать намного меньше данных повторного выполнения, чем обычно. Обратите внимание: "будут генерировать намного меньше", а не "вообще не будут генерировать". Все операции генерируют определенный объем данных повторного выполнения, поскольку все операции со словарем данных журнализируются, независимо от режима. Объем генерируемых данных повторного выполнения может быть намного меньше. Например, я выполнил следующие действия в базе данных, работающей в режиме ARCHIVELOG. Если выполнить их в режиме NOARCHIVELOG, вы не увидите разницы. Оператор CREATE TABLE в базе данных, работающей в режиме NOARCHIVELOG, не будет журнализироваться, за исключением изменений в словаре данных. Пользователи сервера Oracle версии 7.3, однако, увидят различия, поскольку такая оптимизация в этой версии сервера еще не выполнялась. Им также придется использовать ключевое слово UNRECOVERABLE вместо NOLOGGING. В прежних версиях Oracle NOLOGGING (без журнализации) означало UNRECOVERABLE (невосстановимо). Если хочется увидеть отличие при работе базы данных в режиме NOARCHIVELOG, замените операторы DROP TABLE и CREATE TABLE операторами DROP INDEX и CREATE INDEX для какой-либо таблицы. Эти операторы всегда журнализируются, независимо от режима работы сервера. Отсюда и ценный совет: тестируйте систему в том режиме, в котором она будет реально работать, поскольку ее поведение может зависеть от режима. Производственная система должна работать в режиме ARCHIVELOG. Если выполняется много операций, которые в этом режиме генерируют данные повторного выполнения, а в режиме NOARCHIVELOG — нет, то это лучше выяснить в ходе тестирования, а не при вводе в промышленную эксплуатацию! Теперь приведу пример использования конструкции NOLOGGING:

tkyte@TKYTE816> column value new_value old_value tkyte@TKYTE816> select value from redo_size;

VALUE 5195512 tkyte@TKYTE816> create table t 2 as 3 select * from all_objects 4/ Table created. tkyte@TKYTE816> select value-&old_value REDO_GENERATED from redo_size;

old 1: select value-Sold_value REDO_GENERATED from redo_size new 1: select value5195512 REDO_GENERATED from redo_size Глава 5 REDO_GENERATED В моей базе данных сгенерировано более 2,5 Мбайт данных повторного выполнения.

tkyte@TKYTE816> drop t a b l e t;

Table dropped. tkyte@TKYTE816> select value from redo_size;

VALUE 7741248 tkyte@TKYTE816> create table t 2 NOLOGGING 3 as 4 select * from all_objects 5/ Table created. tkyte@TKYTE816> select value-Sold_value REDO_GENERATED from redo_size;

old 1: select value-&old_value REDO_GENERATED from redo_size new 1: select value- 7741248 REDO_GENERATED from redo_size REDO_GENERATED А в этот раз сгенерировано только около 50 Кбайт данных повторного выполнения. Как видите, различие существенное: 2,5 Мбайт данных повторного выполнения или 50 Кбайт. 2,5 Мбайт — это данные таблицы;

они были записаны непосредственно на диск без генерирования данных повторного выполнения. Теперь, конечно, стало понятно, что все операции, для которых это возможно, надо выполнять с опцией NOLOGGING, не так ли? На самом деле, нет. Ее необходимо использовать очень осторожно и только после предварительного согласования с ответственным за резервное копирование и восстановление. Пусть частью приложения является таблица, созданная именно так (например, оператор CREATE TABLE AS SELECT NOLOGGING является частью сценария установки новой версии). Пользователи будут изменять эту таблицу в течение дня. А ночью произойдет сбой диска, на котором находится таблица. "Никаких проблем", — скажет администратор базы данных, — мы же работаем в режиме ARCHIVELOG, так что можем восстановить данные". Проблема, однако, в том, что таблица, созданная как нежурнализируемая, не может быть восстановлена из архивного журнала повторного выполнения. Эта таблица не восстановима, и именно в этом основная особенность операций в режиме NOLOGGING — их использование необходимо согласовать с администратором базы данных, отвечающим за систему в целом. Если вы используете их, а другие пользователи об этом не знают, это сделает невозможным Повторное выполнение и откат восстановление базы данных в случае сбоя носителя. Такие операции надо использовать разумно и осторожно. Про операции, выполняемые с опцией NOLOGGING, важно знать следующее. • Определенный объем данных повторного выполнения на самом деле будет генерироваться. Эти данные обеспечивают защиту словаря данных. Избежать их генерации нельзя. Объем генерируемых данных будет значительно меньше, но генерироваться они все равно будут. • Установка опции NOLOGGING не предотвращает генерирования данных повторного выполнения последующими операторами. В представленном выше примере не была создана таблица, действия с которой не регистрируются в журнале. Все последующие "обычные" действия, выполняемые операторами INSERT, UPDATE и DELETE, будут записываться в журнал. Другие специальные действия, например непосредственная загрузка с помощью утилиты SQLLDR или непосредственные вставки с помощью операторов INSERT / * + APPEND */, регистрироваться в журнале не будут. В общем случае, однако, действия, выполняемые приложениями с этой таблицей, регистрируются в журнале повторного выполнения. • После выполнения действий с опцией NOLOGGING в базе данных, работающей в режиме ARCHIVELOG, необходимо как можно быстрее создать базовую резервную копию затронутых файлов данных. Это необходимо для предотвращения потери последующих изменений соответствующих объектов при сбое носителя. Мы не потеряем сами изменения, поскольку они записываются в журнал повторного выполнения. Будут потеряны данные, к которым эти изменения относятся. Есть два способа использования опции NOLOGGING. Один способ уже продемонстрирован: добавить ключевое слово NOLOGGING в соответствующем месте SQL-оператора. Другой способ позволяет неявно выполнять действия в режиме NOLOGGING. Например, можно изменить индекс так, чтобы он по умолчанию работал в режиме NOLOGGING. Это означает, что при последующем выполнении непосредственных загрузок или непосредственных вставок, затрагивающих этот индекс, изменения не будут регистрироваться (при изменении индекса не будут генерироваться данные повторного выполнения;

для других индексов и самой таблицы — возможно, но для этого индекса — нет). В режиме NOLOGGING возможны следующие действия: • создание и изменение (перестройка) индексов;

• множественные "непосредственные" вставки с помощью подсказки / * + APPEND */;

• действия с большими объектами (изменения больших объектов регистрировать в журнале необязательно);

О создание таблиц с помощью операторов CREATE TABLE AS SELECT;

• изменение таблиц с помощью ALTER TABLE, такие как MOVE и SPLIT;

Глава • выполнение оператора TRUNCATE (но для него указывать конструкцию NOLOGGING не надо, поскольку он всегда выполняется в режиме NOLOGGING). При правильном использовании в базе данных, работающей в режиме ARCHIVELOG, опция NOLOGGING может ускорить выполнение многих действий, существенно уменьшая объем данных, генерируемых в журнал повторного выполнения. Предположим, необходимо перенести таблицу из одного табличного пространства в другое. Эту операцию можно выполнить непосредственно перед резервным копированием: изменить таблицу с помощью оператора ALTER TABLE, переведя ее в режим NOLOGGING, перенести ее, пересоздать индексы (тоже без журнализации), а затем снова перевести таблицу в режим журнализации изменений с помощью оператора ALTER TABLE. В результате этого действие, ранее требовавшее X часов, может выполняться, скажем, Х/2 часов. Правильное использование этой возможности включает согласование с администратором базы данных или ответственным за резервное копирование и восстановление. Если они не знают о ее использовании и произойдет сбой носителя, данные могут быть потеряны. Это надо учитывать.

Не удается выделить новый журнал?

Я постоянно наблюдаю это, хотя и не на своем сервере, конечно. Вы получаете предупреждения об этом (их можно найти в файле журнала сообщений, alert.log, сервера):

Sun Feb 25 10:59:55 2001 Thread 1 cannot allocate new log, sequence 326 Checkpoint not complete В журнале может оказаться сообщение Archival required, а не Checkpoint not complete, но результат — практически тот же. Именно за появлением таких сообщений должен следить администратор базы данных. Если вдруг он этого не делает, вы должны искать их сами. Такие сообщения будут записываться в файл alert.log на сервере при каждой попытке сервера повторно использовать активный журнал повторного выполнения, когда оказывается, что этого делать нельзя. Это будет происходить, когда процесс DBWR еще не закончил обработку контрольной точки для данных, защищаемых этим журналом повторного выполнения, или когда процесс ARCH не закончил копирование файла журнала повторного выполнения в архив. Если названия процессов DBWR и ARCH ничего для вас не значат, почитайте о них подробнее в главе 2. В этот момент сервер фактически останавливается для пользователей. Процесс DBWR или ARCH получает приоритет для сброса блоков на диск. После завершения обработки контрольной точки или архивирования нормальная работа восстанавливается. Причина приостановки выполнения запросов пользователей сервером в том, что нет места для записи выполняемых ими изменений. Сервер Oracle пытается повторно использовать активный файл журнала повторного выполнения. Но поскольку этот файл либо необходим для восстановления базы данных (Checkpoint not complete), либо еще не скопирован в архив (Archival required), серверу Oracle придется подождать (и конечным пользователям тоже), пока файл журнала повторного выполнения будет безопасно использовать.

Повторное выполнение и откат Если оказывается, что сеансы долго ждут событий 'log file switch', 'log buffer space' или 'log file switch checkpoint or archival incomplete', скорее всего вы столкнулись именно с этой проблемой (о том, как узнать, каких событий ожидает сеанс, см. в главе 10). С ней можно столкнуться при продолжительных изменениях базы данных, если файлы журнала повторного выполнения имеют несоответствующий размер или когда требуется настройка работы процессов DBWR и ARCH администратором базы данных или системным администратором. Я часто сталкивался с этой проблемой в стандартной ("starter") базе данных, которую не настраивали. В стандартной базе данных, копируемой при установке с дистрибутивного носителя, журналы повторного выполнения обычно слишком малы для поддержки выполнения существенных действий (включая первоначальное построение словаря данных). При загрузке данных в базу оказывается, что первые 1000 строк вставляются быстро, затем работа продолжается медленно;

1000 строк вставляются быстро, затем сервер зависает, затем опять работает быстро и снова зависает, и т.д. Это признаки описанной выше ситуации. Для решения этой проблемы можно сделать следующее. • Ускорить работу процесса DBWR. Пусть администратор базы данных настроит процесс DBWR: включит поддержку асинхронного ввода/вывода, используя подчиненные процессы ввода/вывода DBWR или запустив несколько процессов DBWR. Посмотрите на статистическую информацию о вводе/выводе в системе — может обнаружиться "горячий" диск или ряд дисков, ввод/вывод на которые надо распределить. Такой же общий совет относится и к процессу ARCH. Преимущество этого решения в том, что оно "бесплатное" — повышение производительности достигается без изменения логики/структур/кода. Недостатков у него по сути нет. • Добавить файлы в журнал повторного выполнения. Это отсрочит возникновение сообщений Checkpoint not complete, причем, после добавления определенного количества — настолько, что они вообще не будут выдаваться (у процесса DBWR появляется пространство для обработки контрольной точки). Это же относится и к сообщению Archival required. Преимущество этого подхода — устранение "пауз" в работе системы. Недостаток — требуется больше дискового пространства, но преимущество в данном случае намного перевешивает недостаток. • Пересоздать файлы журнала, сделав их больше. Это отсрочит заполнение активного файла журнала повторного выполнения и увеличит период времени, после которого он снова понадобится. Это решает и проблему с выдачей сообщений Archival required, если файлы журнала повторного выполнения используются в "пульсирующем" режиме. Если за периодами интенсивного генерирования данных повторного выполнения (еженощные загрузки данных, пакетная обработка) следуют периоды относительного затишья, увеличив активные журналы повторного выполнения, можно дать процессу ARCH дополнительное время для работы в периоды затишья. Преимущества и недостатки этого подхода те же, что и в случае добавления файлов. Кроме того, может быть отсрочена обработка контрольной точки, поскольку она выполняется при переходе с одного логического журнала на другой, а переключения будут происходить реже.

Глава • Вызвать более частую и постоянную обработку контрольной точки. Для этого можно уменьшить буферный кэш (что вообще-то нежелательно) или изменить значения некоторых параметров инициализации, в частности FAST_START_IO_TARGET, DB_BLOCK_MAX_DIRTY_TARGET, LOG_CHECKPOINT_INTERVAL и LOG_CHECKPOINT_TIMEOUT. Это приведет к более частому сбросу грязных блоков на диск процессом DBWR. К преимуществам этого подхода можно отнести уменьшение времени восстановления в случае сбоя. Будет оставаться меньше изменений в активных журналах повторного выполнения для сброса на диск. Недостаток же в том, что блоки будут записываться на диск чаще. Буферный кэш будет работать с меньшей эффективностью, да и весь описанный далее механизм очистки блоков может при этом пострадать. Выбор подхода зависит от обстоятельств. Эту проблему надо решать на уровне всей базы данных, с учетом работы экземпляра в целом.

Очистка блоков Вспомните, в главе 3 я объяснял, что блокировки фактически являются атрибутами данных и хранятся в заголовке блока. Побочный эффект этого в том, что при следующем обращении к блоку может понадобиться очистить его, т.е. удалить информацию о транзакциях. При этом генерируются данные повторного выполнения, и блок становится "грязным", если он таковым еще не был. Это означает, что простой оператор SELECT может генерировать данные повторного выполнения и вызывать запись на диск множества блоков при следующей обработке контрольной точки. В большинстве случаев, однако, этого не происходит. Если транзакции — маленького и среднего размера (ООТ) или выполняется анализ таблиц после множественных операций в хранилище данных, блоки очищаются автоматически. Как было описано ранее, в разделе "Что происходит при фиксации?", при фиксации транзакции происходит повторное обращение к блокам, находящимся в области SGA, если они доступны (другой сеанс их не изменяет), и их очистка. Это действие называют очисткой при фиксации. При фиксации транзакции блоки очищаются настолько, чтобы при последующем выполнении оператора SELECT (чтении) их очищать не пришлось. Только при изменении блока придется удалять находящуюся в нем информацию о транзакции, а поскольку данные повторного выполнения при этом и так генерируются, эта очистка проходит незаметно. Можно принудительно вызвать очистку, чтобы увидеть ее побочные эффекты, если понимать, как выполняется очистка при обработке контрольной точки. Сервер Oracle выделяет списки измененных блоков в списке фиксации, связанном с транзакцией. Каждый их этих списков отслеживает 20 блоков, и сервер Oracle будет выделять столько списков, сколько необходимо. Если суммарный объем измененных транзакцией блоков превысит 10 процентов буферного кэша, сервер Oracle прекратит выделение новых списков. Например, если буферный кэш имеет размер 3000 блоков, сервер Oracle будет автоматически поддерживать список размером до 300 блоков (10 процентов от 3000). При фиксации сервер Oracle будет обрабатывать каждый из выделенных списков по 20 указателей на блоки и, если блок доступен, быстро очистит его. Поэтому, если количество Повторное выполнение и откат измененных блоков не превышает 10 процентов буферного кэша и эти блоки доступны, сервер Oracle очистит их при фиксации транзакции. В противном случае блоки будут просто пропущены (не очищены). Зная это, можно создать искусственные условия для демонстрации того, как происходит очистка. Я установил параметру DB_BLOCK_BUFFERS небольшое значение — 300. Затем создал таблицу, строка которой занимает блок (т.е. двух строк ни в одном блоке не будет). Затем я вставил в эту таблицу 499 строк и выполнил оператор COMMIT. После этого измерил объем сгенерированных данных повторного выполнения, выполнил оператор SELECT, который обращается к каждому измененному блоку, и измерил объем данных повторного выполнения, сгенерированных оператором SELECT. К удивлению многих, оператор SELECT действительно генерирует в этом случае данные повторного выполнения. Более того, измененные им блоки становятся "грязными" и снова сбрасываются на диск процессом DBWR. Это происходит вследствие очистки блоков. Затем я выполнил оператор SELECT еще раз, и он больше не сгенерировал данные повторного выполнения (что вполне предсказуемо, поскольку в этот момент блоки — "чистые").

tkyte@TKYTE816> create table t 2 (x char(2000) default 'x1, 3 у char(2000) default 'y', 4 z char(2000) default 'z') 5/ Table created. tkyte@TKYTE816> insert into t 2 select ' x ', ' у ', ' z ' 3 from all_objects where rownum < 500 4 / 499 rows created. tkyte@TKYTE816> Commit complete. commit;

Итак, вот таблица с одной строкой в каждом блоке (при размере блока базы данных 8 Кбайт). Давайте определим объем данных повторного выполнения, сгенерированных при чтении данных:

tkyte@TKYTE816> column value new_value old_value tkyte@TKYTE816> select * from redo_size;

VALUE 3250592 tkyte@TKYTE816> select 2 from t 3 where x = y;

no rows selected * Глава tkyte@TKYTE816> select value-&old_value REDO_GENERATED from redo_size;

old 1: select value-&old_value REDO_GENERATED from redo_size new 1: select value3250592 REDO_GENERATED from redo_size REDO_GENERATED 29940 tkyte@TKYTE816> commit;

Commit complete.

Итак, при выполнении оператора SELECT было сгенерировано около 30 Кбайт данных повторного выполнения. Она представляет заголовки блоков, измененные при полном просмотре таблицы Т. Процесс DBWR в дальнейшем запишет эти измененные блоки на диск. Если теперь выполнить запрос повторно:

tkyte@TKYTE816> select value from redo_size;

VALUE 3280532 tkyte@TKYTE816> select * 2 from t 3 where x = y;

no rows selected tkyte@TKYTE816> tkyte@TKYTE816> select value-&old_value REDO_GENERATED from redo_size;

old 1: select value-&old_value REDO_GENERATED from redo_size new 1: select valueREDO_GENERATED 0 Commit complete. 3280532 REDO_GENERATED from redo_size то можно убедиться, что данные повторного выполнения не генерируются;

все блоки — чистые. Если снова выполнить этоt пример при размере буферного кэша не 300 блоков, а 6000, окажется, что при выполнении обоих операторов SELECT данные повторного выполнения не генерируются: ни один из блоков при выполнении этих операторов не изменился. Так происходит потому, что 499 измененных блоков составляют менее 10 процентов буферного кэша, а наш сеанс — единственный. Ни один другой сеанс не работает с данными, никто не вызывает сброс данных на диск и не обращается к измененным блокам. В реальных системах блоки иногда не очищаются, и это вполне нормально. Чаще всего подобная ситуация встречается при вставке (как было показано выше), изменении или удалении большого количества строк, когда затрагивается много блоков Повторное выполнение и откат базы данных (при изменении данных объемом более 10 процентов буферного кэша она, определенно, возникнет). Первый же запрос, выполненный к затронутому блоку после этого, сгенерирует немного данных повторного выполнения, сделает блок "грязным" и, кроме того, может вызвать его перезапись, если процесс уже успел сбросить блок на диск или экземпляр был остановлен, что вызвало очистку всего буферного кэша. С этим ничего нельзя поделать. Это нормально и вполне предсказуемо. Если бы сервер Oracle не выполнял такую отложенную очистку блоков, для выполнения оператора COMMIT могло бы потребоваться столько же времени, как и для выполнения самой транзакции. При фиксации пришлось бы повторно обработать каждый затронутый транзакцией блок, возможно, повторно считав его при этом с диска (блок уже мог быть сброшен). Если не учитывать очистку блоков и способ ее выполнения, некоторые события будут казаться загадочными, происходящими без видимой причины. Например, изменен большой объем данных и транзакция зафиксирована. После этого выполняется запрос для проверки результатов. Этот запрос почему-то записывает огромные объемы данных на диск и генерирует большое количество данных повторного выполнения. Это кажется невозможным, если не знать об очистке блоков. Вы пытаетесь кому-то показать это, но ситуация невоспроизводима, поскольку блоки при повторном запросе — "чистые". После этого вы относите ситуацию к разряду необъяснимых "загадок". В системах ООТ с такой ситуацией почти никогда не сталкиваются. Все транзакции — короткие и простые. Изменяется несколько блоков, и они тут же очищаются. В хранилище данных, где выполняются множественные изменения данных после загрузки, очистку блоков необходимо учитывать при проектировании. Некоторые операции будут создавать данные в "чистых" блоках. Например, при выполнении оператора CREATE TABLE AS SELECT, непосредственной загрузке и непосредственной вставке данных создаются "чистые" блоки. Операторы UPDATE, обычные операторы INSERT или DELETE могут создавать блоки, которые придется очищать при первом чтении. Это может повлиять на работу, если выполняются следующие операции: • множественная загрузка новых данных в хранилище данных;

• выполнение изменений всех данных сразу после загрузки (в результате чего создаются блоки, требующие очистки);

• при этом пользователи запрашивают данные. Необходимо учитывать, что первый запрос, обращающийся к данным, потребует дополнительной обработки, если придется очищать блоки. С учетом этого можно самостоятельно "затронуть" данные сразу после изменения. Только что загружен или изменен большой объем данных;

их надо проанализировать. Возможно, придется создать ряд отчетов, проверяющих выполнение загрузки. В результате блоки будут очищены, и следующем запросу не придется это делать. Еще лучше сразу после множественной загрузки данных обновить статистическую информацию — все равно это придется сделать. Выполнение оператора ANALYZE для обновления статистической информации тоже очистит все блоки.

Глава Конфликты при доступе к журналу Эти конфликты, как и сообщения Cannot allocate new log, должен устранять администратор базы данных, как правило, совместно с системным администратором. Однако заметить их может и разработчик, если администраторы следят за сервером недостаточно внимательно. При описании важных представлений динамической производительности V$ в главе 10 мы разберемся, как понять, чего именно ждет сеанс. Часто дольше 1 всего сеансу приходится ждать события 'log file sync. Это значит, что возникают конфликты при доступе к журналам повторного выполнения;

работа с ними выполняется недостаточно быстро. Это может происходить по многим причинам. Одна из причин, связанных с приложениями (ее не может устранить администратор базы данных — это должен делать разработчик), — слишком частая фиксация, например фиксация в цикле после выполнения каждого оператора INSERT. Здесь фиксация выполняется искусственно, исходя из ложного предположения, что так можно сэкономить ресурсы. Слишком частая фиксация, помимо того, что это плохая практика программирования, — еще и гарантированный способ добавить множество ожиданий доступа к журнальным файлам, поскольку придется ждать, пока процесс LGWR сбросит буферы журнала повторного выполнения на диск. Обычно процесс LGWR может это делать в фоновом режиме, и ожидать не придется. При фиксации транзакции также приходится ожидать чаше и дольше, чем это необходимо. Предполагая, что все транзакции имеют правильный размер (фиксация выполняется не чаше, чем это диктуется выполняемыми действиями), основными причинами, вызывающими ожидание доступа к файлам журнала повторного выполнения являются следующие. • Файлы журнала повторного выполнения размещены на низкоскоростном устройстве. Диск просто имеет низкую производительность. Пришло время покупать более высокоскоростные диски. • Размещение журнала повторного выполнения на том же устройстве, что и другие файлы. Журнал повторного выполнения создавался для выполнения больших последовательных записей и размещения на отдельных устройствах. Если другие компоненты системы, даже компоненты сервера Oracle, пытаются читать и записывать данные на это устройство одновременно с процессом LGWR, определенный уровень конфликтов неизбежен. Необходимо обеспечить исключительный доступ процесса LGWR к соответствующим устройствам. • Использование устройств с буферизацией. Речь идет об использовании файлов в файловой системе (вместо неформатированных дисков). Операционная система буферизует данные, СУБД тоже буферизует данные (речь идет о буфере журнала повторного выполнения). Двойная буферизация замедляет работу. По возможности используйте неформатированные устройства. Способ такого использования зависит от операционной системы и устройства, но обычно это возможно. • Размещение журнала повторного устройства на низкоскоростных дисковых массивах, например RAID-5. Массивы RAID-5 прекрасно подходят для чтения, но резко снижают производительность записи. Как было показано ранее, при фик Повторное выполнение и откат сации транзакции необходимо дождаться, пока процесс LGWR сбросит данные на диск. Использование любых технологий, замедляющих процесс записи, — плохая идея. Желательно использовать для журнализации пять отдельных устройств, оптимально — шесть, чтобы можно было выполнять резервное копирование архивов. Сегодня, при наличии дисков объемом 20, 36 и более Гбайт, это становится все сложнее, но если выделить четыре высокоскоростных диска небольшой емкости и один или два диска большей емкости, можно существенно ускорить работу процессов LGWR и ARCH. Эти диски разбиваются на три группы: • группа журналов повторного выполнения 1 — диски 1 и 3 • группа журналов повторного выполнения 2 — диски 2 и 4 • архив — диск 5 и, необязательно, диск 6 (диск большого объема) Нечетные группы (1,3,5) Четные группы (2,4,6) Архивные журналы Диск Диск Диск Диск З Диск Диск Группу журналов повторного выполнения 1 с членами А и Б помещаете в группу 1. Группу журналов повторного выполнения 2 с членами В и Г помещаете в группу 2. Если есть группы 3, 4 и т.д., они размещаются, соответственно, на нечетной и четной группе дисков. При использовании группы 1 процесс LGWR будет записывать на диск 1 и диск 3 одновременно. При заполнении этой группы процесс LGWR перейдет к дискам 2 и 4. Когда эти диски заполнятся, он вернется к дискам 1 и 3. Тем временем процесс ARCH будет обрабатывать заполненные активные журналы повторного выполнения и записывать их в группу 3 на диск большой емкости. В результате ни процесс ARCH, ни процесс LGWR никогда не читает диск, на который идет запись, и не пишет на диск, читаемый другим процессом, т.е. конфликтов нет:

Глава Нечетные группы (1.3,5) Четные группы (2,4,6) Архивные журналы ARCH LGWR ARCH Диск Диск Диск Диск Диск Диск Диск З Диск Диск Диск З Диск Диск Итак, когда процесс LGWR записывает группу 1, процесс ARCH читает группу 2 и записывает ее на диски архива. Когда процесс LGWR записывает группу 2, процесс ARCH читает группу 1 и записывает на диски архива. Таким образом, каждый из процессов LGWR и ARCH использует собственные отдельные устройства и не конфликтует с другими процессами. Файлы журнала повторного выполнения — лишь один из наборов файлов Oracle, эффективность использования которых повышается при размещении на неформатированных устройствах. Если на таких устройствах можно разместить только один набор файлов, это должны быть файлы журнала повторного выполнения. Постоянно идут активные дискуссии о преимуществах и недостатках использования неформатированных устройств по сравнению с файлами в файловых системах. Поскольку эта книга не посвящена задачам администратора базы данных/системного администратора, мы не будем в них вступать. Я просто уточню, что, если предполагается использование неформатированных устройств, лучше всего на них разместить именно файлы журнала повторного выполнения. Выполнять резервное копирование активных файлов журнала повторного выполнения никогда не придется, поэтому факт их размещения на неформатированных разделах вместо файловой системы никак не скажется на сценариях резервного копирования. Процесс ARCH всегда будет преобразовывать "неформатированные" журналы в обычные файлы файловой системы (архивировать на неформатированные устройства нельзя), так что "сложности" работы с неформатированными устройствами в данном случае минимальны.

Временные таблицы и данные повторного выполнения/отката Временные таблицы — новая возможность версии Oracle 8.1.5. Поэтому вокруг них много непонимания, особенно в области журнализации. В главе 6, посвященной типам таблиц базы данных, мы рассмотрим, как и зачем использовать временные таблицы. В Повторное выполнение и откат этом разделе мы рассмотрим только один вопрос: как работают временные таблицы с точки зрения журнализации? Для блоков временных таблиц данные повторного выполнения не генерируются. Поэтому действия с временной таблицей "невосстановимы". При изменении блока во временной таблице запись этих изменений в файлы журнала повторного выполнения не выполняется. Однако при работе с временными таблицами генерируется данные отмены, и эти изменения в сегменте отката журнализируются. Поэтому при работе с временными таблицами генерируется некоторый объем данных повторного выполнения. На первый взгляд это кажется абсолютно бессмысленным: зачем генерировать данные отката? Потому, что в транзакции можно откатиться до точки сохранения. Можно удалить последних 50 вставленных во временную таблицу строк, но не первых 50. Для временных таблиц можно задавать ограничения и все прочие конструкции, доступные для обычных таблиц. Может произойти сбой на 500 строке оператора вставки 500 строк, т.е. потребуется откат оператора. Поскольку временные таблицы ведут себя так же, как "обычные", при работе с ними обязательно должны генерироваться данные отката. Поскольку данные в сегменте отката должны журнализироваться, при генерировании данных отката будет генерироваться и некоторый объем данных повторного выполнения. Все не так страшно, как кажется. С временными таблицами используются в основном SQL-операторы INSERT и SELECT. К счастью, операторы INSERT генерируют немного данных отмены (блок необходимо восстановить в состояние "ничего", а для хранения "ничего" надо не так уж много места), а операторы SELECT данные отмены вообще не генерируют. Поэтому, если временные таблицы используются исключительно для вставки и запросов, этот раздел вполне можно проигнорировать. Учитывать генерирование данных отмены надо только при использовании операторов UPDATE или DELETE. Я создал небольшой тестовый пример, демонстрирующий объем генерируемых при работе с временными таблицами данных повторного выполнения и показывающий косвенно, сколько генерируется данных отмены, поскольку для них регистрируются только изменения в сегменте отката. Для этого используются "постоянная" и "временная" таблицы идентичной структуры, с которыми выполняются одинаковые действия, а затем определяется объем сгенерированных данных повторного выполнения. Я использовал следующие простые таблицы:

tkyte@TKYTE816> create table 2 (x char(2000) default 3 у char(2000) default 4 z char(2000) default 5 / Table created. tkyte@TKYTE816> tkyte@TKYTE816> tkyte@TKYTE816> create global temporary table temp 2 (x char(2000) default 'x', 3 у char(2000) default 'y'. perm 'x', 'y', 'z') 4 5 Глава 5 z char(2000) default 'z') on commit preserve rows / Table created.

Затем, я создал небольшую хранимую процедуру для применения указанного SQLоператора к таблице и выдачи отчета о результатах:

tkyte@TKYTE816> create or replace procedure do_sql(p_sql in varchar2) 2 as 3 l_start_redo number;

4 l_redo number;

5 begin 6 select value into l_start_redo from redo_size;

7 8 execute immediate p sql;

9 commit;

10 11 select value-l_start_redo into l_redo from redo_size;

12 13 dbms_output.put_line 14 (to_char(l_redo, '9,999,999') ||' bytes of redo generated for '" || 15 substr(replace(p_sql, chr(10), ' ' ), 1, 25) || ' "... ' ) ;

16 end;

17 / Procedure created.

Затем я применил к таблицам одинаковые операторы INSERT, UPDATE и DELETE:

tkyte@TKYTE816> set serveroutput on format wrapped tkyte@TKYTE816> begin 2 do_sql (' insert into perm 3 select 1,1,1 4 from all_objects 5 where rownum <= 500');

6 7 do_sql('insert into temp 8 select 1,1,1 9 from all_objects 10 where rownum <= 500');

11 12 do_sql( 'update perm set x = 2');

13 do_sql( 'update temp set x = 2') ;

14 15 do_sql ('delete from perm');

16 do_sql ('delete from temp'),;

17 end;

18 / 3,238,688 bytes of redo generated for "insert into perm"... 72,572 bytes of redo generated for "insert into temp"... 2,166,376 bytes of redo generated for "update perm set x = 2"...

Повторное выполнение и откат 1,090,336 3,320,244 3,198,236 bytes bytes bytes of redo of redo of redo generated generated generated for "update temp set x = 2"... for "delete from perm"... for "delete from temp"...

PL/SQL procedure successfully completed.

Этот пример демонстрирует следующее. • В процессе вставки в "реальную" таблицу генерируется много данных повторного выполнения. Для временной таблицы данные повторного выполнения практически не генерируются. В этом есть смысл: операторы вставки генерируют очень мало данных отмены и только изменения данных отмены для временных таблиц регистрируются в журнале. • При изменении реальной таблицы генерируется примерно вдвое больше данных повторного выполнения, чем при изменении временной таблицы. И это тоже понятно. Необходимо сохранить примерно половину изменения, "предварительный образ". "Окончательный образ" (данные повторного выполнения) для временной таблицы сохранять не надо. • При удалении строк генерируется примерно одинаковый объем данных повторного выполнения. Это понятно, потому что данные отмены для оператора DELETE имеют большой объем, а данные повторного выполнения для измененных блоков невелики. Поэтому оператор DELETE с временной таблицей работает почти так же, как и с обычной. Поэтому при работе с временными таблицами можно использовать следующие простые правила: • оператор INSERT генерирует мало или вообще не генерирует данные отмены/ повторного выполнения;

• оператор DELETE генерирует тот же объем данных повторного выполнения, что и для обычной таблицы;

• оператор UPDATE генерирует примерно вдвое меньше данных повторного выполнения, чем для обычной таблицы. Из этого правила есть исключения. Например, при замене пустого (NULL) столбца 2000 байт данных оператор UPDATE сгенерирует очень мало данных для отката. Такое изменение подобно вставке. С другой стороны, если столбец с 2000 байт данных становится пустым (NULL), такое изменение подобно удалению с точки зрения объема генерируемых данных повторного выполнения. В среднем можно ожидать, что при изменении временной таблицы генерируется примерно 50 процентов данных отмены/ повторного выполнения от объема для обычной таблицы. При определении объема создаваемых данных повторного выполнения можно руководствоваться здравым смыслом. Если выполняемая операция приводит к созданию данных отмены, подумайте, насколько легко или сложно будет восстановить (отменить) результат операции. Вставку 2000 байт отменить просто. Надо вернуть ситуацию, когда было 0 байт. Если удалено 2000 байт, для восстановления придется 2000 байт вставить. В этом случае генерируется значительный объем данных повторного выполнения.

Глава Учитывая все это, избегайте удаления строк из временных таблиц. Можно использовать оператор TRUNCATE или дождаться автоматической очистки при фиксировании транзакции или удалении сеанса. Все эти методы не генерируют данные отмены и, как следствие, данные повторного выполнения. Старайтесь не изменять временные таблицы, если для этого нет серьезной причины. Временные таблицы используются в основном для вставки данных и запросов. При этом оптимально используется их уникальная возможность — не генерировать данные повторного выполнения.

Анализ данных повторного выполнения Завершая рассмотрение файлов журнала повторного выполнения, попробуем ответить на часто задаваемый вопрос: как проанализировать эти файлы? Ранее, до появления версии Oracle 8i, это было очень сложно. Приходилось обращаться в службу поддержки Oracle, чтобы получить магическую команду для перевода содержимого файла журнала повторного выполнения в текстовый вид. В результате создавался текстовый отчет, который можно было читать и, имея некоторое представление о внутренних, недокументированных особенностях СУБД Oracle, даже приходить к каким-то выводам. С практической точки зрения это было неприемлемо, особенно если надо было проанализировать файлы журнала повторного выполнения и найти, скажем, время удаления таблицы или установки отрицательного значения столбца SALARY в транзакции и т.п. И тут появился Log Miner. Речь идет о новом пакете, DBMS_LOGMNR, поставляемом в составе Oracle 8i и позволяющем загружать содержимое файлов журнала повторного выполнения в таблицу базы данных V$, а затем выполнять к ней SQL-запросы. Можно получить предварительное и окончательное значение каждого столбца, затронутого оператором ЯМД. Можно получить SQL-оператор, фактически "повторно выполняющий" или "отменяющий" транзакцию. В этой таблице можно найти оператор удаления из таблицы OBJ$ (принадлежащая пользователю SYS таблица словаря данных) и увидеть, когда именно была случайно удалена таблица. Вдруг администратор базы данных сможет вернуть базу данных в состояние на момент времени, непосредственно предшествующий выполнению этого оператора DROP TABLE и восстановить таблицу. Пока я просто упомяну Log Miner. В приложении А пакет DBMS_LOGMNR будет описан детально. Подробнее о встроенных пакетах см. в последней части книги.

Откат Были уже затронуты многие темы, связанные с сегментами отката. Мы рассматривали, как они используются в ходе восстановления, как взаимодействуют с журналами повторного выполнения, а также упоминали, что они используются для обеспечения согласованного, не блокирующего чтения данных. В этом разделе я хочу описать проблемы, связанные с сегментами отката. Основную часть времени мы посвятим печально известной ошибке ORA-01555: snapshot too old, но до этого разберемся еще с двумя проблемами, касающимися отката. Сначала мы выясним, какие действия генерируют наибольший/наименьший объем данных отмены (возможно, вы и сами, после изучения предыдущих примеров с временными таблицами, сможете ответить). Затем рассмотрим, Повторное выполнение и откат как оператор SET TRANSACTION используется для указания сегмента отката, и обсудим, и каких случаях нужно его использовать. И только после этого подробно рассмотрим загадочную ошибку ORA-01555.

Что генерирует основной/наименьший объем данных отмены?

Ответить на этот вопрос очень легко. Наименьший объем информации отмены генерирует оператор INSERT, поскольку серверу Oracle достаточно записать идентификатор строки для удаления. Оператор UPDATE по этому показателю обычно идет вторым. Достаточно записать только измененные байты. Чаше всего изменяется лишь небольшая часть данных строки. Поэтому только небольшую часть строки придется записывать в сегмент отката. Многие из представленных ранее примеров демонстрировали как раз обратное, но лишь потому, что изменялись большие строки фиксированной длины, причем изменялись целиком. Гораздо чаще изменяется лишь небольшая часть строки. Оператор DELETE, как правило, генерирует наибольший объем данных отмены. Для оператора DELETE сервер Oracle должен записать в сегмент отката предварительный образ всей строки в целом. Представленный ранее пример, в котором генерировались данные повторного выполнения для временной таблицы, прекрасно это продемонстрировал. Оператор INSERT сгенерировал очень немного данных отмены, которую пришлось регистрировать в журнале. Оператор UPDATE сгенерировал данные, равные по объему предварительным образам измененных данных, а оператор DELETE сгенерировал всю строку данных, которая и была записана в сегмент отката.

Оператор SET TRANSACTION SQL-оператор SET TRANSACTION позволяет задать сегмент отката, который должна использовать транзакция. Обычно это делается для выделения большого сегмента отката для операции, затрагивающей большой объем данных. Мне не нравится эта практика, особенно, когда ею злоупотребляют. Для нечастых множественных изменений это вполне приемлемо как разовое действие. Я, однако, считаю, что надо использовать сегменты отката одинакового размера, а выбор сегмента для транзакции предоставить системе. При использовании сегментов отката, которые могут увеличиваться (если параметр MAXEXTENTS имеет достаточно большое значение) и для которых установлен размер OPTIMAL (т.е. они уменьшаются до некоторого размера после существенного увеличения), отдельный большой сегмент не нужен. Однако, если применяется один большой сегмент отката, ничто не мешает другой транзакции его использовать. Практически ничего нельзя сделать, чтобы отдать сегмент отката одной транзакции. В выделенном пространстве будут работать и другие транзакции. Гораздо проще дать системе самой выбрать сегмент отката и разрешить увеличивать его при необходимости. Кроме того, некоторые инструментальные средства не разрешают выбирать сегмент отката, например утилита IMP (импорт). При обновлении моментального снимка, однако, указывать сегмент отката можно, а при использовании Глава утилиты SQLLDR — нет. Я считаю, что все сегменты отката должны иметь достаточный размер для выполняемых в системе транзакций. С учетом всего этого, для рассматриваемого оператора остается нечастое, однократное применение. При выполнении множественных изменений большого объема информации его использование оправдано. Создается временный сегмент отката на одном из дополнительных дисков, и выполняется изменение. После изменения этот сегмент отката отключается и удаляется. Вероятно, еще лучше сначала фрагментировать большую таблицу и выполнять изменение параллельно. В этом случае, каждый из подчиненных процессов параллельного запроса будет привязан к отдельному сегменту отката, в результате чего транзакция может использовать все сегменты отката одновременно. Этого нельзя достичь при выполнении изменения последовательно.

'ORA-01555: snapshot too old' Ошибка ORA-01555 — одна из ошибок, сбивающих людей с толку. С ней связано много мифов, неточностей и предположений. На самом деле эта ошибка очевидна и возникает только по двум причинам, но поскольку специальный случай одной из этих причин встречается слишком часто, можно выделить три причины: • сегменты отката слишком малы для выполняемых в системе действий;

• программы извлекают данные между фиксациями (по сути это разновидность первой причины);

• очистка блоков. Первые две причины непосредственно связаны с моделью согласованности по чтению Oracle. Если вспомнить главу 2, посвященную архитектуре, результаты запроса "предопределены" — они хорошо известны еще до извлечения сервером Oracle первой строки. Сервер Oracle обеспечивает этот согласованный по времени моментальный снимок базы данных, используя сегменты отката для отмены изменений в блоках, изменившихся с начала выполнения запроса. Любой выполняемый оператор, например:

update t set x = 5 where x = 2;

insert into t select * from t where x = 2;

delete from t where x = 2;

select * from t where x = 2;

получит согласованное по чтению представление таблицы Т и набор строк, в которых х=2, независимо от других действий, параллельно выполняемых в базе данных. Все операторы, читающие таблицу, используют этот механизм согласованности по чтению. В представленном выше примере оператор UPDATE читает таблицу, чтобы найти в ней строки, где х=2 (и затем изменить их). Оператор INSERT читает таблицу в поисках строк, где х=2, и затем вставляет их, и так далее. Использование сегментов отката для этих двух целей — отката неудачных транзакций и обеспечения согласованности по чтению — и приводит к возникновению ошибки ORA-01555. Третья причина возникновения ошибки ORA-01555 более загадочна, поскольку в этом случае ошибка может возникать в базе данных с единственным сеансом, причем — не из Повторное выполнение и откат меняющим таблицу! Это кажется невозможным;

зачем нужны данные в сегменте отката для таблицы, которая не изменяется? Далее мы разберемся с этим. Прежде чем проиллюстрировать все три причины, хочу поделиться своим опытом устранения проблем, связанных с ошибкой. ORA-1555. В общем случае возможны следующие решения. • Проанализировать используемые объекты. Это поможет избежать третьей причины. Поскольку очистка блоков выполняется в результате множественных изменений или вставок очень большого объема данных, это все равно надо делать. • Увеличить или добавить сегменты отката. Это уменьшает вероятность перезаписи данных отмены при выполнении продолжительного запроса. Это решение помогает устранить все три перечисленные ранее причины. • Сократить время выполнения запроса (настроить запрос). Это наилучший выход, поэтому именно к нему следует прибегнуть в первую очередь. В результате сократится потребность в больших сегментах отката. Это решение помогает устранить все три причины возникновения ошибки. Мы вернемся к этим решениям позже, однако в общих чертах опишем их здесь.

Сегменты отката очень маленькие Сценарий следующий: имеется система, транзакции в которой — маленькие. Естественно, в сегментах отката требуется очень мало места. Рассмотрим, например, следующую ситуацию. • Каждая транзакция генерирует в среднем 1 Кбайт данных отмены. • В среднем выполняется пять таких транзакций в секунду (генерируется 5 Кбайт данных отмены в секунду, 300 Кбайт в минуту). • В среднем раз в минуту выполняется транзакция, генерирующая 1 Мбайт данных отмены. В результате генерируется примерно 1,3 Мбайт данных отмены в минуту. • В системе сконфигурированы сегменты отката общим размером 5 Мбайт. Для обработки транзакции места в сегментах отката в этой базе данных предостаточно. Система будет переходить на следующий сегмент отката и использовать место в нем повторно в среднем раз в три-четыре минуты. Если размер сегментов отката определен на основе этой информации о предполагаемых транзакциях, — все в порядке. В этой же среде, однако, надо еще создавать отчеты. Некоторые из запросов выполняются весьма долго, — скажем, пять минут. Вот тут и возникает проблема. Если запросы выполняются пять минут и им понадобится представление данных на момент начала запроса, велика вероятность возникновения ошибки ORA-01555. Поскольку сегменты отката в ходе выполнения этого запроса переписываются, понятно, что часть данных отмены, сгенерированных с начала выполнения запроса, потеряна, — они были перезаписаны. Если мы столкнемся с блоком, измененным сразу после начала запроса, данные отмены для этого блока не будет найдены, и мы получим сообщение об ошибке ORA-01555.

Глава Рассмотрим небольшой пример. Пусть имеется таблица с блоками 1, 2, 3,... 1000000. Вот последовательный список событий, которые могли произойти: Время (минут: секунд) 0:00 0: Действие Начато выполнение запроса. Другой сеанс изменяет блок 1000000. Данные отмены для этого изменения записываются в определенный сегмент отката. Этот сеанс фиксирует транзакцию. Сгенерированные данные отмены все еще доступны, но могут быть перезаписаны при нехватке места в сегменте отката. Запрос продолжает выполняться. Сейчас он просматривает блок 200000. Выполняется много действий. В результате, сгенерировано уже более 1,3 Мбайт данных отмены. Запрос все еще выполняется. Он уже дошел до блока 600000. Сегменты отката начинают перезаписываться, и пространство, использовавшееся в начале выполнения запроса, в момент 0:00, начинает использоваться повторно. В частности, мы только что повторно использовали в сегменте отката место, где были записаны данные отмены для блока 1000000, измененного в момент времени 0:01. Запрос добрался, наконец, до блока 1000000. Оказывается, блок был изменен после начала выполнения запроса. Сервер обращается к сегменту отката и пытается найти данные отмены для этого блока, чтобы получить его согласованное по времени представление. В этот момент выясняется, что необходимой информации больше нет. Возникает ошибка ORA-01555, и запрос заканчивается неудачно.

0: 1:00 1:01 3:00 4: 5: Сегменты отката начинают перезаписываться, и пространство, использовавшееся в начале выполнения запроса, в момент 0:00, начинает использоваться повторно. В частности, мы только что повторно использовали в сегменте отката место, где были записаны данные отмены для блока 1000000, измененного в момент времени 0:01. 5:00 Запрос добрался, наконец, до блока 1000000. Оказывается, блок был изменен после начала выполнения запроса. Сервер обращается к сегменту отката и пытается найти данные отмены для этого блока, чтобы получить его согласованное по времени представление. В этот момент выясняется, что необходимой информации больше нет. Возникает ошибка ORA-01555, и запрос заканчивается неудачно. Вот и все. Если сегменты отката имеют такой размер, что могут быть перезаписаны по ходу выполнения запросов, и запросы эти обращаются к данным, которые могли быть 4: Повторное выполнение и откат только при выполнении операторов INSERT, UPDATE и DELETE. Факт выполнения продолжительного запроса не заставляет сервер Oracle увеличить сегмент отката, чтобы при необходимости сохранить его данные. Это может сделать только продолжительная транзакция, изменяющая данные. В представленном примере, хотя сегменты отката могли расти, они не росли. Для этой системы надо изначально создать большие сегменты отката. Необходимо выделить постоянное пространство для сегментов отката, а не просто дать возможность расти при необходимости. Для решения этой проблемы надо либо создать сегменты отката такого размера, чтобы они переписывались не чаще, чем один раз в шесть-десять минут, либо сделать так, чтобы запросы выполнялись не более двух-трех минут. Первое решение основано на том факте, что имеются запросы, выполняющиеся пять минут. В этом случае администратор базы данных должен увеличить объем постоянно выделяемых сегментов отката в дватри раза. Второе решение тоже вполне допустимо. Если можно ускорить выполнение запросов, это надо делать. Если блоки в сегментах отката, сгенерированные с начала запроса, никогда не перезаписываются, ошибка ORA-01555 не возникнет. Важно помнить, что вероятность возникновения ошибки ORA-01555 определяется размером наименьшего сегмента отката в системе. Добавление одного "большого" сегмента отката не устранит проблему. Достаточно при выполнении запроса повторно использовать самый маленький сегмент отката, и сразу появляется возможность возникновения ошибки ORA-01555. Вот почему мне очень нравится идея создавать сегменты отката одинакового размера. При этом каждый сегмент отката — одновременно наибольший и наименьший. Вот почему я также избегаю использования сегментов отката "оптимального" размера. При сжатии вынужденно увеличенного сегмента отката выбрасывается множество данных отмены, которые могут еще понадобиться. Хотя удаляются самые старые данные в сегменте отката, определенный риск все же остается. Я предпочитаю уменьшать сегменты отката в периоды минимальной нагрузки вручную, если это вообще делается. Пожалуй, я слишком глубоко вошел в роль администратора базы данных, так что давайте перейдем к следующему случаю: ошибка ORA-01555 вызвана тем, что размер сегментов отката для текущего объема изменений определен неправильно. Единственное решение — правильно задавать размер сегментов отката в соответствии с объемом изменений. Это не ваша ошибка, но это ваша проблема, раз уж вы с ней столкнулись. Эта ситуация аналогична нехватке временного пространства в ходе выполнения запроса. Надо либо предусмотреть достаточное количество временного пространства в системе, либо переписать запросы так, чтобы при выполнении для них требовалось меньше временного пространства. Чтобы продемонстрировать этот эффект, можно создать небольшой тестовый пример. Ниже я создам очень маленький сегмент отката. Будет работать только один сеанс, использующий только этот сегмент отката, что практически гарантирует перезапись и многократное повторное использование выделенного пространства. Сеанс, использующий этот сегмент отката, будет изменять таблицу Т. Он будет выполнять полный просмотр таблицы Т, и читать ее от начала до конца. В другом сеансе мы выполним запрос к таблице Т, который будет читать ее по индексу. Т.е. он будет читать строки таблицы в относительно случайном порядке. Он прочитает строку 1, затем — строку 1000, стоку Глава 500, строку 20001 и т.д. При выполнении запроса блоки считываются в случайном порядке и, вероятно, по несколько раз. Вероятность возникновения ошибки ORA-01555 в этом случае составляет почти 100 процентов. Итак, в одном сеансе выполним:

tkyte@TKYTE816> create rollback segment rbs_small 2 storage 3 (initial 8k next 8k 4 minextents 2 maxextents 3) 5 tablespace rbs_test 6/ Rollback segment created. tkyte@TKYTE816> alter rollback segment rbs_small online;

Rollback segment altered. tkyte@TKYTE816> create table t 2 as 3 select * 4 from all objects 5 / Table created. tkyte@TKYTE816> create index t_idx on t(object_id) 2/ Index created. tkyte@TKYTE816> 2 3 4 5 6 7 8 9 10 11 end;

12 / begin for x in (select rowid rid from t) loop commit;

set transaction use rollback segment rbs_small;

update t set object_name = lower(object_name) where rowid = x.rid;

end loop;

commit;

Пока выполняется этот блок PL/SQL, в другом сеансе выполним:

tkyte@TKYTE816> select object_name from t where object_id > 0 order by object_id;

OBJECT_NAME i_obj# tab$ /91196853_activationactivation Повторное выполнение и откат /91196853_activationactivation ERROR: ORA-01555: snapshot too old: rollback "RBS_SMALL" too small 3150 rows selected. tkyte@TKYTE816> select count(*) COUNT (*) 21773 from t;

segment number with name Как видите, после чтения около трех тысяч строк (примерно одной седьмой части данных) в произвольном порядке, мы получили-таки сообщение об ошибке ORA-01555. В этом случае ошибка была вызвана исключительно чтением таблицы Т по индексу, в результате чего в случайном порядке читались блоки по всей таблице. Если бы выполнялся полный просмотр таблицы, то вероятность того, что ошибка ORA-01555 не возникла бы, гораздо больше. (Попробуйте изменить запрос SELECT на SELECT / * + FULL(T) */... и посмотрите, что произойдет;

в моей системе даже многократное выполнение этого запроса не приводит к возникновению ошибки ORA-1555). Поскольку операторы SELECT и UPDATE будут последовательно просматривать таблицу Т, то оператор SELECT скорее всего опередит в просмотре UPDATE (оператор SELECT только читает данные, a UPDATE должен прочитать их и изменить, поэтому выполняется этот оператор медленнее). Выполняя случайные чтения, мы увеличиваем вероятность того, что оператору SELECT понадобится прочитать блок, давно измененный и зафиксированный оператором UPDATE. Этот пример демонстрирует некоторую неоднозначность ошибки ORA-01555. Ее возникновение зависит от того, как одновременно работающие сеансы читают и изменяют таблицы.

Данные извлекаются в нескольких транзакциях Это разновидность той же ситуации. Вариант этот не отличается от предыдущего, но сеанс создает проблемы сам себе. Никаких действий со стороны других сеансов не нужно. Мы уже изучали эту ситуацию в главе 4, посвященной транзакциям, и кратко рассмотрим ее снова. Смысл в том, что извлечение строк, перемежающееся выполнением операторов COMMIT, — верный способ получить сообщение об ошибке ORA-01555. По моим наблюдениям, большинство ошибок ORA-01555 связано с такими действиями. Удивительно, но разработчики иногда реагируют на получение этой ошибки тем, что фиксируют транзакции еще чаще, поскольку в сообщении сказано, что сегмент отката — слишком мал. Предполагают, что это поможет решить проблему (исходя из ошибочного вывода, что при изменении используется слишком много места в сегменте отката), тогда как на самом деле это приводит лишь к скорейшему возникновению этой ошибки. Итак, надо изменить большой объем информации. По некоторым причинам вы не обеспечили достаточно места в сегментах отката. Поэтому принимается решение фик Глава сировать транзакцию каждые X строк, чтобы "сэкономить" место в сегментах отката. Мало того, что это медленнее, и в конечном итоге будет сгенерировано больше данных отмены и повторного выполнения, это еше и гарантированный способ получить сообщение об ошибке ORA-01555. Продолжая предыдущий пример, я могу легко это продемонстрировать. Используя ту же таблицу Т, что и в примере выше, просто выполните:

tkyte@TKYTE816> declare 2 l_cnt number default 0;

3 begin 4 for x in (select rowid rid, t.* from t where object_id > 0) 5 loop 6 if (mod(l_cnt,100) = 0) 7 then 8 commit;

9 set transaction use rollback segment rbs_small;

10 end if;

11 update t 12 set object_name = lower(object_name) 13 where rowid = x.rid;

14 l_cnt := l_cnt + 1;

15 end loop;

16 commit;

17 end;

18 / declare * ERROR at line 1: ORA-01555: snapshot too old: rollback segment number 10 with name "RBS_SMALL" too small ORA-06512: at line Здесь выполняется случайное чтение таблицы Т по индексу. Мы изменяем по одной строке за раз в цикле. После изменения 100 строк транзакция фиксируется. Рано или поздно мы повторно обратимся в запросе к блоку, уже измененному оператором UPDATE, и этот блок уже не удастся восстановить по сегментам отката (поскольку данные в них давно перезаписаны). Теперь мы попали в неприятную ситуацию: процесс изменения завершился неудачей на полпути. Можно, как было показано в главе 4, придумать более изощренный метод изменения данных. Например, найти минимальный и максимальный идентификатор OBJECT_ID. Можно поделить этот диапазон на поддиапазоны по 100 строк и делать изменения, записывая в другую таблицу информацию об успешно выполненных действиях. Это позволит реализовать процесс, который должен быть реализован одним оператором и одной транзакцией, в виде множества отдельных транзакций, организованных с помощью сложного процедурного кода таким образом, чтобы они могли быть перезапущены в случае сбоя. Например:

Повторное выполнение и откат tkyte@TKYTE816> create table done(object_id int);

Table created. tkyte@TKYTE816> insert into done values (0) ;

1 row created. tkyte@TKYTE816> declare 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 l_cnt number;

l_max number;

begin select object_id into l_cnt from done;

select max(object_id) into l_max from t;

while loop (l_cnt < l_max) update set where and t object_name = lower(object_name) object_id > l_cnt object_id <= l_cnt+100;

update done set object_id = object_id+100;

commit;

set transaction use rollback segment rbs_small;

l_cnt := l_cnt + 100;

end loop;

end;

/ PL/SQL procedure successfully completed.

Мы предложили сложное решение, которое надо еще тестировать и анализировать. И работать оно будет намного медленнее, чем простой оператор:

update t set object_name = lower(object_name) where object_id > 0;

Простое и, по моему мнению, правильное решение этой дилеммы, — сконфигурировать для системы сегменты отката достаточного размера и использовать один оператор UPDATE. При изменении большего, чем обычно, объема данных используйте "черновой" сегмент отката, создаваемый специально для больших процессов, и удалите его после этого. Это решение гораздо лучше сложного процедурного решения, которое может не сработать просто в силу своей сложности (из-за ошибки программирования). Проще дать системе сделать все необходимое, чем пытаться придумать сложные обходные пути, позволяющие "сэкономить место".

Отложенная очистка блоков Эту причину ошибки ORA-01555 полностью устранить сложнее, но она встречается реже, поскольку слишком редки обстоятельства, при которых она возникает (по крайней мере, в версии Oracle 8i). Мы уже рассматривали механизм очистки блоков: сеанс, Глава обратившийся к измененному блоку, проверяет, активна ли еще транзакция, изменившая его. Если оказалось, что транзакция не активна, он очищает блок так, что следующему обращающемуся к нему сеансу не придется выполнять этот процесс. Чтобы очистить блок, сервер Oracle находит (по заголовку блока) сегмент отката, использованный в предыдущей транзакции, а затем проверяет по заголовку сегмента отката, была ли транзакция зафиксирована. Эта проверка выполняется одним из двух способов. В одном из них используется способность сервера Oracle определить, что транзакция зафиксирована, даже если ее слот в таблице транзакций сегмента отката был перезаписан. Другой способ — найти в таблице транзакций сегмента отката номер зафиксированного изменения (COMMIT SCN), что свидетельствует о недавней фиксации транзакции, поскольку ее слот еще не перезаписан. При отложенной очистке блока ошибка ORA-01555 может возникать в следующих случаях. • Изменение выполнено и зафиксировано, а блоки не очищены автоматически (например, транзакция изменила больше блоков, чем поместится в 10 процентах пространства буферного кэша в области SGA). • Эти блоки не затронуты другим сеансом: их затронет наш невезучий запрос. • Начался "продолжительный" запрос. Он рано или поздно прочитает некоторые из описанных выше блоков. Этот запрос начинается при значении SCN t l. Именно до этого номера изменения в системе придется откатывать данные для того, чтобы обеспечить согласованность чтения. Запись для транзакции, изменившей данные, в момент начала выполнения запроса еще находится в таблице транзакций сегмента отката. • В ходе выполнения запроса в системе зафиксировано много транзакций. Эти транзакции не затронули рассматриваемые блоки (если бы затронули, мы бы с этой проблемой не столкнулись). • Таблица транзакций в сегменте отката переполняется, и ее слоты используются повторно из-за большого количества зафиксированных транзакций. Что хуже всего, запись исходно изменившей данные транзакции тоже была использована повторно и переписана. Кроме того, система повторно использовала экстенты в сегменте отката, что не позволяет согласовать по чтению блок заголовка сегмента отката. • Наименьший номер изменения SCN, записанный в сегменте отката, теперь превосходит tl (он больше, чем согласованный по чтению номер SCN для запроса) из-за большого количества зафиксированных транзакций. Теперь, когда наш запрос доберется до блока, измененного и зафиксированного до его начала, возникнут проблемы. Обычно запрос обращается к сегменту отката, на который указывает блок, и узнает состояние изменившей его транзакции (другими словами, находит номер изменения SCN для оператора фиксации транзакции). Если это значение SCN меньше, чем t l, запрос может использовать этот блок. Если же значение номера изменения SCN для оператора COMMIT превосходит t l, запрос должен отка Повторное выполнение и откат тить этот блок. Проблема, однако, в том, что запрос в этом случае не может определить: больше номер изменения COMMIT SCN для блока, чем t l, или меньше. Непонятно, можно использовать блок или нет. В результате выдается сообщение об ошибке ORA-01555. Можно искусственно вызвать возникновение этой ошибки с помощью одного сеанса, но более впечатляющим получится результат при использовании двух сеансов, поскольку можно будет показать, что ошибка не вызвана извлечением данных в нескольких транзакциях. Мы продемонстрируем оба примера, поскольку они невелики по объему и очень похожи. Создадим много блоков в таблице, которые требуют очистки. Затем откроем курсор для таблицы и в небольшом цикле инициируем множество транзакций. Я помещаю все изменения в один и тот же сегмент отката, чтобы гарантировано получить нужную ошибку. Рано или поздно произойдет ошибка ORA-01555, поскольку внешний запрос в цикле (SELECT * FROM T) столкнется с проблемой очистки блока из-за частого изменения и фиксации во внутреннем цикле:

tkyte@TKYTE816> create table small(x int) ;

Table created. tkyte@TKYTE816> insert into small values 1 row created. tkyte@TKYTE816> begin 2 commit;

3 set transaction use rollback segment rbs_small;

4 update t 5 set object_type = lower(object_type);

6 commit;

7 8 for x in (select * from t) 9 loop 10 for i in 1.. 20 11 loop 12 update small set x = x+1;

13 commit;

14 set transaction use rollback segment rbs_small;

15 end loop;

16 end loop;

17 end;

18 / begin * ERROR at line 1: ORA-01555: snapshot too old: rollback segment number 10 with name "RBS_SMALL" too small ORA-06512: at line 8 tkyte@TKYTE816> select * from small;

(0) ;

Глава 5 X Для возникновения представленной выше ошибки я использовал ту же таблицу Т с примерно 22000 строк, как и в прежних примерах (в моей системе она занимает около 300 блоков). У меня сконфигурирован буферный кэш размером 300 блоков (10 процентов от него составляет 30 блоков). Оператор UPDATE в строке 4 должен оставить около 270 блоков, требующих очистки. Затем мы выполняем оператор SELECT * из таблицы, в которой требуется очистить множество блоков. Для каждой извлекаемой строки выполняется 20 транзакций, причем я предусмотрел, чтобы эти транзакции направлялись в тот же сегмент отката, что и первоначальный оператор UPDATE (конечная цель — переписать его слот в таблице транзакций). После обработки около 10000 строк в запросе к таблице Т (о чем свидетельствует значение в таблице SMALL) мы получили сообщение об ошибке ORA-01555. Теперь, чтобы показать, что это не связано с извлечением данных в нескольких транзакциях (это вполне возможно, поскольку при извлечении данных выполняются операторы COMMIT), мы используем два сеанса. Первый сеанс мы начнем с создания таблицы STOP_OTHER_SESSION. Ее мы будем использовать для уведомления другого сеанса, генерирующего множество транзакций, что пора остановиться. Затем, сеанс изменит множество блоков в таблице еще раз, как в предыдущем примере, и начнет продолжительный запрос к таблице. Процедура DBMS_LOCK. SLEEP используется для реализации ожидания между извлечениями строк, что увеличивает время выполнения запроса. Это имитирует время выполнения, необходимое для сложной обработки каждой строки:

tkyte@TKYTE816> create table stop_other_session (x int) ;

Table created. tkyte@TKYTE816> declare 2 l_cnt number := 0;

3 begin 4 commit;

5 set transaction use rollback segment rbs small;

6 update t 7 set object_type - lower(object_type);

8 commit;

9 10 for x in (select * from t) 11 loop 12 dbms_lock.sleep(1);

13 end loop;

14 end;

15 / Теперь, пока выполняется представленный выше код, в другом сеансе необходимо выполнить следующее:

Повторное выполнение и откат tkyte@TKYTE816> create table small(x int) ;

Table created. tkyte@TKYTE816> insert into small values (0) ;

1 row created.

tkyte@TKYTE816> begin 2 commit;

3 set transaction use rollback segment rbs_small;

4 for i in 1.. 500000 5 loop 6 update small set x = x+1;

7 commit;

8 set transaction use rollback segment rbs_small;

9 for x in (select * from stop_other_session) 10 loop 11 return;

— остановиться, когда другой сеанс попросит это — сделать 12 end loop;

13 end loop;

14 end;

15 / PL/SQL procedure successfully completed.

Через некоторое время в первом сеансе будет получено такое сообщение: declare * E R R at l i n e 1: RO ORA-01555: snapshot too o l d : rollback segment number 10 with name "RBS_SMALL" too small ORA-06512: at l i n e Сообщение об ошибке — то же самое, но теперь мы не извлекали данные в нескольких транзакциях, т.е. читаемые данные никем не изменялись. Как я уже упоминал, это — редкий случай. Необходимо стечение многих обстоятельств. Необходимо наличие блоков, требующих очистки, а такие блоки в версии Oracle8i встречаются редко (это случалось в версиях 7.x и 8.0). Оператор сбора статистики ANALYZE избавляет от этих блоков, поэтому большинство обычных причин их возникновения, в частности множественные изменения и загрузку большого объема данных, можно не учитывать, поскольку после таких действий таблицы все равно надо анализировать. Транзакции затрагивают менее 10 процентов блоков в буферном кэше, т.е. не генерируют блоки, требующие очистки. Если вы уверены, что столкнулись именно с этой проблемой (оператор SELECT к таблице, к которой не применяются при этом операторы ЯМД, заканчивается сообщением об ошибке ORA-01555), попытайтесь сделать следующее. • Прежде всего убедитесь, что используете транзакции соответствующего размера. Проверьте, что не фиксируете транзакции чаще, чем это необходимо.

Глава • Проанализируйте соответствующие объекты. Поскольку очистка блоков требуется в результате очень больших множественных изменений или вставок данных, это все равно надо сделать. • Увеличьте размер сегментов отката или создайте дополнительные. Благодаря этому уменьшится вероятность перезаписи слота таблицы транзакций сегмента отката в случае продолжительного запроса. Кроме того, это позволит избежать других причин возникновения ошибки ORA-01555 (фактически эти две причины тесно связаны;

при обработке запроса вы сталкиваетесь с повторным использованием сегмента отката). • Сократите время выполнения запроса (настройте его). В случае успеха это будет удачным решением, поэтому именно с этого и стоит начать.

Резюме В этой главе мы рассмотрели значение для разработчика данных повторного выполнения и отмены. Здесь я представил в основном те проблемы, которые касаются администратора базы данных или системного администратора. Я показал важность данных повторного выполнения и отмены и продемонстрировал, что она не является излишней тратой ресурсов: журналы повторного выполнения и сегменты отката являются неотъемлемыми и необходимыми компонентами базы данных. Глубоко понимая их работу и назначение, вы сможете использовать их с максимальной отдачей. Кроме того, вы должны усвоить, что при более частой, чем необходимо, фиксации ничего не "экономится" (фактически ресурсы транжирятся: необходимо больше процессорного времени, больше места на диске и больше программирования). Разберитесь, что должен делать сервер баз данных, и позвольте ему функционировать нормально.

Таблицы В этой главе мы рассмотрим таблицы базы данных. Будут представлены различные типы таблиц и показано, в каких случаях имеет смысл использовать каждый из них. Будут детально описаны особенности физического хранения таблиц и организацию их данных. Когда-то был только один тип таблиц — "обычные". Они управлялись как "куча" (ее определение представлено ниже). Со временем в Oracle добавились более сложные типы таблиц. Теперь, кроме обычных таблиц, есть еще кластерные (двух типов), таблицы, организованные по индексу, вложенные таблицы, временные и объектные. Каждый тип таблиц имеет свои характеристики, делающие его наиболее подходящим в тех или иных прикладных областях.

Типы таблиц Прежде чем перейти к деталям, определим каждый тип таблиц. В Oracle 8i — семь основных типов таблиц. • Таблицы, организованные в виде кучи. Это "обычные", стандартные таблицы базы данных. Данные управляются по принципу "кучи". При добавлении данных используется первое же свободное место в сегменте, достаточное для их размещения. При удалении данных из таблицы освободившееся место может повторно использоваться следующими операторами INSERT и UPDATE. Вот откуда название "организованные в виде кучи" для таких таблиц. "Куча" — это пространство памяти, используемое достаточно случайным образом. • Таблицы, организованные по индексу. Такая таблица хранится в структуре индекса. Это предполагает физическое упорядочение ее строк. Если в обычной таблице данные вставляются в любое свободное место, в таблице, организованной по индексу, хранимые данные отсортированы по первичному ключу.

Глава • Таблицы в кластере. Хранение таблицы в кластере дает два преимущества. Вопервых, несколько таблиц можно хранить вместе. Обычно в блоке хранятся данные только одной таблицы. В кластере же в одном блоке могут храниться данные нескольких таблиц. Во-вторых, все данные, содержащие одно и то же значение ключа кластера, также хранятся вместе. Данные "кластеризованы" вокруг значения ключа кластера. Ключ кластера строится с помощью индекса на основе В*дерева. • Таблицы в хеш-кластере. Аналогичны представленным ранее таблицам в кластере, но, вместо индекса на основе В*-дерева, для поиска блока данных по ключу кластера используется хеширование ключа. В хеш-кластере сами данные (образно говоря) и есть индекс. Это хорошо подходит для чтения данных, соответствующих определенному значению ключа. • Вложенные таблицы. Часть объектно-реляционных расширений сервера Oracle. Это просто генерируемые и поддерживаемые системой дочерние таблицы связанные как предок-потомок. Они устроены аналогично таблицам ЕМР и DEPT в схеме SCOTT. Таблицу ЕМР можно считать дочерней по отношению к таблице DEPT, поскольку в таблице ЕМР есть внешний ключ, DEPTNO, ссылающийся на таблицу DEPT. Главное различие в том, что это — не "отдельные" таблицы, как ЕМР. • Временные таблицы. В этих таблицах сохраняются черновые данные на время транзакции или сеанса. При необходимости для этих таблиц выделяются временные экстенты из временного табличного пространства пользователя. Каждый сеанс будет "видеть" только выделенные им самим экстенты и никогда не "увидит" данные, созданные в других сеансах. • Объектные таблицы. Это таблицы, создаваемые на основе объектного типа. Они имеют специальные атрибуты, отсутствующие у необъектных таблиц, например генерируемый системой псевдостолбец REF (идентификатор объекта) для каждой строки. Объектные таблицы фактически являются отдельным случаем обычных, организованных по индексу и временных таблиц, и могут включать вложенные таблицы. Имеется несколько общих свойств таблиц, не зависящих от их типа. • Таблица может иметь до 1000 столбцов, хотя я не рекомендовал бы использовать такие таблицы без крайней необходимости. Таблицы наиболее эффективно работают при количестве столбцов, намного меньшем, чем 1000. • Таблица может иметь практически неограниченное количество строк. Хотя при этом придется столкнуться с другими ограничениями. Например, табличное пространство может обычно состоять не более чем из 1022 файлов. Пусть используются файлы размером 32 Гбайт, тогда получаем 32704 Гбайт в каждом табличном пространстве. Это означает — 2143289344 блока по 16 Кбайт каждый. В один такой блок можно вместить 160 строк размером от 80 до 100 байт. Это дает в итоге 342926295040 строк. Однако если фрагментировать таблицу, это количество можно увеличить в десять раз. Теоретически ограничения, конечно, есть, но прежде чем они будут достигнуты, придется столкнуться с другими, практическими ограничениями.

Таблицы • Таблица может иметь столько индексов, сколько имеется перестановок столбцов (и перестановок функций от этих столбцов), но не более 32 столбцов, хотя и в этом случае будут практические ограничения на количество реально создаваемых и сопровождаемых индексов. • Нет ограничения на количество таблиц. И в этом случае практические ограничения будут держать количество таблиц в разумных границах. Миллионов таблиц у вас не будет (такое количество сложно создать и поддерживать), но тысячи таблиц поддерживаются элементарно. Начнем с рассмотрения параметров и терминов, относящихся к таблицам. После этого будут описаны простые таблицы, организованные в виде "кучи", а затем — и другие типы таблиц.

Терминология В этом разделе мы рассмотрим различные параметры хранения и терминологию, связанную с таблицами. Не все описываемые параметры используются для каждого типа таблиц. Например, параметр PCTUSED не имеет смысла для таблицы, организованной по индексу. Применимые параметры будут указаны при описании каждого типа таблиц. Цель этого раздела — представить термины и дать их определения. При необходимости, более детальная информация об использовании конкретных параметров будет представлена в следующих разделах.

Отметка максимального уровня Этот термин (high water mark) используется для объектов, хранящихся в базе данных. Если представить себе таблицу как "линейную" структуру в виде последовательности блоков слева направо, то отметкой максимального уровня будет крайний правый блок, когда-либо содержавший данные. Например:

отметка максимального уровня в только что созданной таблице отметка максимального уровня после вставки 10000 строк отметка максимального уровня после удаления 5000 строк Глава Этот рисунок показывает, что отметка максимального уровня сначала указывает на первый блок только что созданной таблицы. По мере добавления данных в таблицу и использования новых блоков отметка максимального уровня повышается. Если удалить некоторые (или даже все) строки таблицы, может появиться много блоков, уже не содержащих данные, но они все равно находятся до отметки максимального уровня и останутся там, пока объект не будет пересоздан или усечен. Отметка максимального уровня важна, поскольку сервер Oracle при полном просмотре будет сканировать все блоки до этой отметки, даже если они не содержат данных. Это скажется на скорости выполнения полного просмотра, особенно если большая часть блоков до отметки максимального уровня — пустые. Чтобы убедиться в этом, просто создайте таблицу из 1000000 (или другого большого количества) строк. Выполните запрос SELECT COUNT(*) к этой таблице. Теперь удалите все строки таблицы и убедитесь, что запрос SELECT COUNT(*), не возвращающий строк, работает так же долго, как и в случае возвращения 1000000 строк. Так происходит потому, что сервер Oracle просматривает все блоки до отметки максимального уровня в поисках данных. Сравните это с результатом применения оператора TRUNCATE. При выполнении TRUNCATE отметка максимального уровня будет сброшена "в ноль". Если предполагается удаление всех строк таблицы, именно по этой причине надо использовать оператор TRUNCATE.

Списки свободных мест В списке свободных мест (FREELIST) сервер Oracle отслеживает блоки объекта до отметки максимального уровня, в которых есть свободное пространство. С каждым объектом будет связан хотя бы один список свободных мест, и по мере использования блоков они будут при необходимости добавляться или удаляться из этого списка. Важно отметить, что в списке свободных мест будут только блоки, находящиеся до отметки максимального уровня. Блоки за отметкой максимального уровня будут использоваться, только когда списки свободных мест пусты, — в этот момент сервер Oracle переносит отметку максимального уровня дальше и добавляет соответствующие блоки в список свободных мест. Таким образом, сервер Oracle откладывает увеличение отметки максимального уровня для объекта, пока это не станет действительно необходимым. У объекта может быть несколько списков свободных мест. Если предполагается выполнение значительного количества операций INSERT или UPDATE для объекта множеством одновременно работающих пользователей, создание нескольких списков свободных мест может существенно повысить производительность (за счет возможного использования дополнительной памяти). Как будет показано далее, наличие достаточного количества списков свободных мест принципиально важно. В среде с множеством одновременных вставок или изменений данных списки свободных мест могут как снижать так и повышать производительность. Предельно простой тест может показать преимущества установки подходящего количества списков свободных мест. Берем простейшую таблицу:

tkyte@TKYTE816> c r e a t e table t (x i n t ) ;

и с помощью двух сеансов начинаем интенсивно вставлять в нее данные. Если измерить общесистемное количество ожиданий событий, связанных с блоками, до и после Таблицы этой операции, можно выявить продолжительные ожидания доступности блоков данных (при вставке данных). Это зачастую вызвано недостаточным количеством списков свободных мест в таблицах (и в индексах, но к индексам мы еще вернемся в главе 7). Итак, я создал временную таблицу:

tkyte@TKYTE816> create global temporary table waitstat_before 2 on commit preserve rows 3 as 4 select * from v$waitstat 5 where 1=0 6/ Table created.

для хранения исходного количества ожиданий блоков. Затем в двух сеансах я одновременно выполнил:

tkyte@TKYTE816> truncate table waitstat_before;

Table truncated. tkyte@TKYTE816> insert into waitstat_before 2 select * from v$waitstat 3 / 14 rows created. tkyte@TKYTE816> begin 2 for i in 1.. 100000 3 loop 4 insert into t values 5 commit;

6 end loop;

7 end;

8/ PL/SQL procedure successfully completed.

(i) ;

Это — очень простой блок кода, и мы — единственные пользователи базы данных в этот момент. Производительность при этом должна быть максимальной. У меня создан большой буферный кэш, журналы повторного выполнения имеют нужный размер, индексы не замедляют работу;

все должно работать быстро. В результате, однако, я получаю следующее:

tkyte@TKYTE816> select a.class, b.count-a.count count, b.time-a.time time 2 from waitstat_before a, v$waitstat b 3 where a.class = b.class 4 / CLASS bitmap block COUNT 0 TIME Глава 6 0 4226 0 0 0 0 2 0 0 0 0 649 0 0 3239 0 0 0 0 0 0 0 0 0 36 bitmap index block data block extant map free list save undo block save undo header segment header sort block system undo block system undo header undo block undo header unused В ходе одновременной работы этих сеансов пришлось ждать более 32 секунд. Это целиком следствие недостаточного количества списков свободных мест для одновременного выполнения подобного рода действий. Можно легко вообще избавиться от этого ожидания, просто создав таблицу с несколькими списками свободных мест:

tkyte@TKYTE816> create t a b l e t (x i n t ) storage (FREELISTS 2 ) ;

Table created, или изменив уже существующий объект:

tkyte@TKYTE816> a l t e r table t storage (FREELISTS 2) ;

Table a l t e r e d.

Теперь можно убедиться, что оба показанных ранее времени ожидания свелись к нулю. Для таблицы необходимо попытаться определить максимальное количество одновременных вставок или изменений, для выполнения которых потребуется выделение дополнительного пространства. Под одновременностью я имею в виду ситуации, когда несколько сеансов в один и тот же момент будут запрашивать свободный блок для соответствующей таблицы. Речь не идет о количестве одновременно выполняющихся транзакций — нас интересует, сколько сеансов будет одновременно выполнять вставку, независимо от границ транзакций. Для достижения максимального параллелизма необходимо как минимум столько списков свободных мест, сколько вставок одновременно выполняется в таблицу. Значит, задав большое значение параметра FREELIST, можно забыть про все эти проблемы, верно? Нет, — это было бы слишком просто. Каждый процесс будет использовать один список свободных мест. Он не будет при поиске свободного места переходить от одного списка к другому. Это означает, что если в списке, используемом процессом, свободного места не осталось, он не будет искать свободное место в другом списке. И хотя в таблице имеется десять списков свободных мест, будет передвинута отметка максимального уровня для таблицы. Если же передвигать отметку некуда (все пространство использовано), будет выделен новый экстент. Затем процесс продолжит использовать пространство только в своем списке свободных мест (который сейчас пуст). При использовании нескольких списков свободных мест приходится идти на компромисс. С Таблицы одной стороны, несколько списков свободных мест позволяют существенно повысить производительность. С другой стороны, таблица будет занимать больше пространства на диске, чем необходимо. Придется решать, что важнее. Не стоит недооценивать полезность этого параметра, особенно с учетом возможности изменять его при желании в версиях, начиная с Oracle 8.1.6. Можно увеличить его значение для параллельной загрузки данных в обычном режиме с помощью утилиты SQLLDR. При этом будет достигнута высокая степень параллелизма загрузки с минимальными ожиданиями. После загрузки можно вернуть прежнее, более подходящее для обычной работы, значение параметра FREELIST. При этом блоки из нескольких существующих списков свободных мест будут объединены в один основной список свободных мест.

Параметры PCTFREE и PCTUSED Эти два параметра управляют добавлением и удалением блоков из списков свободных мест. При задании для таблицы (но не для таблицы, организованной по индексу, как будет показано далее) параметр PCTFREE сообщает серверу Oracle, сколько места должно быть зарезервировано в блоке для будущих изменений. Стандартное значение — 10 процентов. Предположим, используются блоки размером 8 Кбайт. Когда при добавлении новой строки в блок свободного места в блоке останется меньше 800 байт, сервер Oracle будет использовать новый блок вместо существующего. Эти 10 процентов пространства данных в блоке оставляются для изменений строк блока. Если строку придется изменять, в блоке будет место для размещения измененной строки. Итак, если параметр PCTFREE определяет, когда сервер Oracle убирает блок из списка свободных мест, чтобы в него больше не вставлялись строки, то параметр PCTUSED определяет, когда сервер Oracle снова вернет блок в список свободных мест. Если параметр PCTUSED установлен равным 40 процентам (стандартное значение) и в блоке достигнут уровень заполнения PCTFREE (блок уже не находится в списке свободных мест), то сервер Oracle вернет его в список, только когда в блоке станет свободным 61 процент пространства. При использовании стандартных значений параметров PCTFREE (10) и PCTUSED (40) блок будет оставаться в списке свободных мест, пока не заполнится на 90 процентов (в нем останется 10 процентов свободного пространства). Как только он заполнится на 90 процентов, то будет удален из списка свободных мест и не попадет в него, пока свободное пространство не составит 60 процентов блока. Параметры PCTFREE и PCTUSED реализуются по-разному для разных типов таблиц, на что при описании этих типов я буду обращать внимание. Для некоторых типов таблиц используются оба параметра, для других — только PCTFREE, да и то лишь при создании объекта. Есть три значения параметра PCTFREE: слишком большое, слишком маленькое и подходящее. Если установить слишком большое значение параметра PCTFREE для блоков, будет напрасно расходоваться пространство на диске. Если установить значение PCTFREE равным 50 процентам, а данные никогда не изменяются, то 50 процентов каждого блока просто пустуют. В другой таблице, однако, значение 50 процентов может быть вполне уместным. Если первоначально маленькие строки со временем увеличиваются Глава примерно вдвое, большое значение параметра PCTFREE позволит избежать переноса строк.

Перенос строки Итак, когда переносится строка? Строка переносится из блока, в котором она была создана, когда она выросла настолько, что уже не помещается в блоке с остальными строками. Перенос строки проиллюстрирован ниже. Все начинается с блока, который выглядит примерно так:

- заголовок блока свободное место данные строки 4 данные строки 3 данные строки 2 свободное место данные строки Примерно одна седьмая блока — свободное место. Однако необходимо более чем в два раза увеличить занимаемое строкой 4 место с помощью оператора UPDATE (сейчас эта строка занимает одну седьмую часть блока). В данном случае, даже если сервер Oracle объединит все свободное пространство в блоке в один фрагмент следующим образом:

заголовок блока свободное место данные строки 4 данные строки 3 данные строки данные строки все равно для увеличения строки 4 более чем в два раза места не хватит, потому что общее свободное пространство меньше, чем текущий размер строки 4. Если бы строка могла поместиться в свободное пространство после его объединения, то это объединение было бы выполнено. В нашем случае, однако, сервер Oracle не будет выполнять объединение, и блок останется неизменным. Поскольку строка 4 должна выйти за границы блока, сервер Oracle перенесет ее. Но она, однако, не будет просто перемещена;

ее не Таблицы обходимо оставить как "адрес для пересылки". Могут существовать индексы, физически ссылающиеся на текущий адрес строки 4. При простом изменении эти индексы не будут обновляться. (Учтите, что в случае использования фрагментированных таблиц идентификатор, или адрес, строки будет меняться;

этот особый случай мы рассмотрим в главе 14, посвященной фрагментации.) Поэтому когда сервер Oracle переносит строку, он оставляет по старому адресу указатель на ее реальное местонахождение. После изменения блоки могут выглядеть примерно так:

заголовок блока свободное место данные строки данные строки 3 данные строки заголовок блока свободное место свободное место данные строки перемещенные данные строки Итак, вот что такое перенесенная строка: это строка, которую пришлось перенести из блока, в который она была вставлена, в какой-то другой блок. Почему это надо учитывать? Приложение никогда о переносе строки не "узнает", SQL-операторы менять не придется. Это влияет только на производительность. Если придется читать эту строку через индекс, он будет ссылаться на исходный блок. Этот блок, в свою очередь, будет ссылаться на новый блок. Вместо выполнения, скажем, двух операций ввода/вывода для чтения индекса и одной для чтения таблицы, для получения фактических данных строки потребуется еще одна дополнительная операция ввода/вывода блока. Сама по себе это "небольшая" проблема;

вы эту дополнительную операцию даже не заметите. Однако, если в этом состоянии окажется значительная часть строк и обращаться к таблице будет множество пользователей, этот побочный эффект будет заметен. Доступ к данным начнет замедляться (дополнительная операция ввода/вывода увеличивает время доступа), эффективность буферного кэша — снижаться (придется буферизовать в два раза больше блоков, чем при отсутствии переноса строк), а размер и сложность таблицы — расти. Именно по этим причинам переноса строк следует избегать. Интересно разобраться, что сервер Oracle будет делать, если перенесенную с левого блока (на представленной ранее схеме) в правый блок строку снова придется переносить. Это может понадобиться, поскольку в блок, куда она была перенесена, добавлены строки, а наша строка опять увеличивается при изменении. Сервер Oracle перенесет строку назад в исходный блок и, если в нем достаточно места, оставит ее там (строка становится "неперенесенной"). Если места в исходном блоке недостаточно, сервер Oracle перенесет всю строку в другой блок и изменит адрес перенаправления в исходном блоке. Таким образом, перенос строк всегда создает один уровень косвенной ссылки. Итак, теперь мы можем вернуться к параметру PCTFREE и его использованию;

при правильной установке значения этого параметра можно свести перенос строк к минимуму.

Глава Установка значений PCTFREE и PCTUSED Установка значений параметров PCTFREE и PCTUSED — важная тема, которой не всегда уделяется должное внимание. Я хочу продемонстрировать, как можно оценить дисковое пространство, используемое объектами схемы. Я буду использовать хранимую процедуру, демонстрирующую последствия вставки в таблицу данных при различных значениях параметров PCTFREE/PCTUSED, после чего выполняется ряд изменений одних и тех же данных. Это покажет, как эти параметры могут повлиять на количество блоков, доступных в списке FREELIST (что, в конечном итоге, влияет на использование пространства, количество перенесенных строк и т.д.). Предлагаемые сценарии — иллюстративные;

они не определяют оптимальные значения параметров. Их можно использовать, чтобы разобраться, как сервер Oracle работает с блоками при выполнении различных изменений. Для эффективного использования эти шаблоны сценариев придется изменить. Я начну с создания тестовой таблицы:

tkyte@TKYTE816> create table t (x int, у char(1000) default 'x');

Table created.

Она очень проста, но для демонстрации прекрасно подойдет. Использование типа CHAR гарантирует, что все строки с непустым значением в столбце Y будут иметь длину чуть более 1000 байт. Поэтому можно предположить, что будет происходить с блоками определенного размера. Теперь представлю процедуру для оценки списка FREELIST и использования пространства в блоке:

tkyte@TKYTE816> create or replace procedure measure_usage 2 as 3 l_f ree_blks number;

4 l_total_blocks number;

5 l_total_bytes number;

6 l_unused_blocks number;

7 l_unused_bytes number;

8 l_LastUsedExtFileId number;

9 l_LastUsedExtBlockId number;

10 1_LAST_OSED_BLOCK number;

11 12 procedure get_data 13 is 14 begin 15 dbms_space.free_blocks 16 (segment_owner => USER, 17 segment_name => 'T', 18 segment_type => 'TABLE', 19 FREELIST_group_id => 0, 20 free_blks => l_free_blks);

21 22 dbms_space.unused_space 23 (segment_owner => USER, Таблицы 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 segment_name => 'T', segment_type => 'TABLE', total_blocks => i_total_blocks, total_bytes => l_total_bytes, unused_blocks => l_unused_blocks, unused_bytes => l_unused_bytes, IAST_USED_EXTENT_FILE_ID => l_LastOsedExtFileId, IAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId, LAST_USED_BLOCK => l_last_used_block);

dbms_output.put_line(L_free_blks || ' on FREELIST, ' || to_char(1 total_blocks-l unused_blocks-l) ' used by table') ;

end;

begin for i in 0.. 10 loop dbms_output.put('insert ' || to_char(i,'00') || ' ' ) ;

get_data;

insert into t (x) values (i) ;

commit;

end loop ;

|| for i in 0.. 10 loop dbms_output.put('update ' || to_char(i,'00') || ' ' ) ;

get_data;

update t set у = null where x = i;

commit;

end loop;

end;

/ Procedure created.

Здесь мы используем две подпрограммы пакета DBMS_SPACE, информирующие о том, сколько блоков находится в списке FREELIST сегмента, сколько — выделено таблице, сколько — не используется и т.д. С помощью этой информации можно определить, сколько используемых таблицей блоков (до отметки максимального уровня таблицы) находятся в списке FREELIST. Затем я вставляю в таблицу 10 строк с непустым значением в столбце Y. После этого построчно изменяю столбец Y так, чтобы он имел значение Null. С учетом того, что размер блока базы данных — 8 Кбайт, при стандартных значениях PCTFREE — 10 процентов и PCTUSED — 40 процентов можно ожидать, что семь строк свободно поместятся в блок (представленный ниже расчет сделан без учета служебных областей в блоке/строке):

Глава (2+1) байт для X + (1000+2) байт для Y = 1005 1005 байт/строку * 7 строк = 7035 8192 - 7035 байт (размер блока) = 1157 байт Остается 1157 байт, что недостаточно для еще одной строки плюс чуть больше 800 байт (10% блока) Поскольку 10 процентов от блока размером 8 Кбайт составляет немногим более 800 байт, понятно, что еще одна строка в блок не поместится. При желании можно точно рассчитать размер заголовка блока;

здесь мы лишь предположили, что он поместится в 350 с небольшим байтах (1157 - 800 = 357). Это позволяет разместить в блоке семь строк. Затем определим, сколько изменений понадобится, чтобы блок вернулся в список FREELIST. Мы знаем, что для этого блок должен использоваться менее чем на 40 процентов, т.е. для возврата в список свободных в блоке может использоваться не более 3275 байт. Тогда, если при выполнении каждого оператора UPDATE освобождается 1000 байт, для возвращения блока обратно в список FREELIST потребуется выполнить примерно четыре оператора UPDATE. Ну что же, давайте посмотрим, насколько хорошо я посчитал:

tkyte@TKYTE816> exec measure_usage;

insert 00 0 on FREELIST, 0 used by insert 01 1 on FREELIST, 1 used by insert 02 1 on FREELIST, 1 used by insert 03 1 on FREELIST, 1 used by insert 04 1 on FREELIST, 1 used by insert 05 1 on FREELIST, 1 used by insert 06 1 on FREELIST, 1 used by insert 07 1 on FREELIST, 1 used by insert 08 1 on FREELIST, 2 used by insert 09 1 on FREELIST, 2 used by insert 10 1 on FREELIST, 2 used by update 00 1 on FREELIST, 2 used by update 01 1 on FREELIST, 2 used by update 02 1 on FREELIST, 2 used by update 03 1 on FREELIST, 2 used by update 04 2 on FREELIST, 2 used by update 05 2 on FREELIST, 2 used by update 06 2 on FREELIST, 2 used by update 07 2 on FREELIST, 2 used by update 08 2 on FREELIST, 2 used by update 09 2 on FREELIST, 2 used by update 10 2 on FREELIST, 2 used by table table table table table table table table — table table table table table table table table — table table table table table table между 7-й к 8-й строками добавился еще один "используемый" блок четвертое изменение возвращаетблок в список свободных PL/SQL procedure successfully completed.

Понятно, что после семи вставок в таблицу добавляется еще один блок. Аналогично, после четырех изменений, количество блоков в списке FREELIST увеличивается с 1 до 2 (оба блока опять находятся в списке свободных — в них можно вставлять строки). Если удалить и пересоздать таблицу Т с другими установками и выполнить измерение еще раз, мы получим следующее:

Таблицы tkyte@TKYTE816> create table t (x int, у char(1000) default 'x') pctfree 10 2 pctused 80;

Table created. tkyte@TKYTE816> exec measure_usage;

insert 00 0 on FREELIST, 0 used by insert 01 1 on FREELIST, 1 used by insert 02 1 on FREELIST, 1 used by insert 03 1 on FREELIST, 1 used by insert 04 1 on FREELIST, 1 used by insert 05 1 on FREELIST, 1 used by insert 06 1 on FREELIST, 1 used by insert 07 1 on FREELIST, 1 used by insert 08 1 on FREELIST, 2 used by insert 09 1 on FREELIST, 2 used by insert 10 1 on FREELIST, 2 used by update 00 1 on FREELIST, 2 used by update 01 2 on FREELIST, 2 used by update 02 2 on FREELIST, 2 used by update 03 2 on FREELIST, 2 used by update 04 2 on FREELIST, 2 used by update 05 2 on FREELIST, 2 used by update 06 2 on FREELIST, 2 used by update 07 2 on FREELIST, 2 used by update 08 2 on FREELIST, 2 used by update 09 2 on FREELIST, 2 used by update 10 2 on FREELIST, 2 used by table table table table table table table table table table table table table — первое же изменение вернуло table блох в список свободных из-за table намного большего значения table pctused table table table table table table PL/SQL procedure successfully completed.

Это демонстрирует последствия увеличения значения параметра PCTUSED. Первое же изменение вернуло блок в список FREELIST. Этот блок может использоваться для вставки новой строки намного быстрее. Значит ли это, что необходимо увеличивать значение параметра PCTUSED? Нет, не обязательно. Это зависит от того, что впоследствии происходит с данными. Пусть приложение работает по следующему циклу: 1. Добавление данных (множество вставок). 2. Изменение данных, которое приводит к увеличению и уменьшению строк. 3. Снова добавление данных. Тогда вообще не нужно, чтобы блоки возвращались в список свободных в результате изменения. Желательно установить параметру PCTUSED небольшое значение, чтобы блок попадал в список FREELIST только после удаления из него всех строк данных. В противном случае некоторые из блоков с временно уменьшенными строками при большом значении PCTUSED будут использованы для вставки новых строк. Затем, когда придется изменять старые и новые строки в этих блоках, для них не хватит места и придется часть строк переносить.

Глава Итак, параметры PCTUSED и PCTFREE имеют ключевое значение. С одной стороны, их нужно задавать, чтобы избежать переноса слишком большого количества строк, а с другой, — чтобы избежать избыточных потерь пространства в блоках. Необходимо изучить объекты и их предполагаемое использование, а затем выработать обоснованный план установки значений этих параметров. Простые правила могут и не подойти;

оптимальные значения действительно зависят от способов использования объектов. Имеет смысл рассмотреть следующие варианты (помните, большое и небольшое — понятия относительные). • Большое значение PCTFREE, небольшое значение PCTUSED. Для вставки большого количества динамических данных, если при их изменении длина строк часто увеличивается. При этом резервируется много свободного пространства в блоке после вставки (большое значение PCTFREE);

прежде чем блок будет возвращен в список свободных, он должен стать практически пустым (небольшое значение PCTUSED). • Небольшое значение PCTFREE, большое значение PCTUSED. Если предполагается только вставка и удаление строк или если при изменении строки ее длина только уменьшается.

Параметры INITIAL, NEXT и PCTINCREASE Эти параметры хранения определяют размер начального (INITIAL) и последующих экстентов, выделяемых таблице, а также процент увеличения для следующих экстентов. Например, если используется начальный экстент размером 1 Мбайт, следующий — размером 2 Мбайта, а параметр PCTINCREASE имеет значение 50, в таблице будут следующие экстенты: 1. 1 Мбайт. 2. 2 Мбайт. 3. 3 Мбайт (150 процентов от 2). 4. 4,5 Мбайт (150 процентов от 3). и т.д. Я считаю эти параметры устаревшими. В базе данных необходимо использовать только локально управляемые табличные пространства с одинаковыми экстентами. При этом размер первого экстента всегда совпадает с размером следующего, а параметр PCTINCREASE вообще не имеет смысла, — он лишь вызывает фрагментацию табличного пространства. Если локально управляемые табличные пространства не используются, я рекомендую всегда устанавливать INITIAL = NEXT и PCTINCREASE = 0. Это позволяет эмулировать процесс выделения пространства, происходящий в локально управляемых табличных пространствах. Для всех объектов в табличном пространстве должна использоваться одинаковая стратегия выделения экстентов во избежание фрагментации.

Таблицы Параметры MINEXTENTS и MAXEXTENTS Эти параметры управляют количеством экстентов, которые могут быть выделены объекту. Значение MINEXTENTS указывает серверу Oracle, сколько экстентов выделять таблице первоначально. Например, в локально управляемом табличном пространстве с одинаковыми экстентами размером 1 Мбайт при установке параметру MINEXTENTS значения 10 таблице будет выделено 10 Мбайт дискового пространства. Параметр MAXEXTENTS задает верхний предел для количества экстентов, которые могут быть выделены объекту. Если в том же табличном пространстве задать параметру MAXEXTENTS значение 255, таблица сможет иметь размер не более 255 Мбайт. Конечно, если в табличном пространстве не хватит места для обеспечения такого роста, выделить соответствующие экстенты таблице тоже не удастся.

Параметры LOGGING и NOLOGGING Обычно объекты создаются с журнализацией (LOGGING), тем самым все действия с объектом, которые могут генерировать информацию повторного выполнения, ее генерируют. Параметр NOLOGGING позволяет выполнять с объектом ряд действий, не генерируя данных повторного выполнения. Параметр NOLOGGING затрагивает лишь несколько специфических действий: первоначальное создание объекта, непосредственная загрузка данных с помощью утилиты SQLLDR или пересоздание (подробнее о том, какие из этих действий применимы к тому или иному объекту базы данных, см. в справочном руководстве по языку SQL — "SQL Language Reference Manual"). Этот параметр не отключает генерирование данных повторного выполнения для объекта вообще, а только для некоторых специфических действий. Например, если таблица создается как SELECT NOLOGGING, а затем выполняется оператор INSERT INTO THAT_TABLE VALUES (1), этот оператор будет зарегистрирован в журнале, а создание таблицы — нет.

Параметры INITRANS and MAXTRANS Каждый блок данных объекта включает заголовок. Часть этого заголовка — таблица транзакций. В таблицу транзакций вносятся записи о том, какие строки/элементы блока заблокированы какими транзакциями. Первоначальный размер таблицы транзакций определяется параметром INITRANS объекта. Для таблиц стандартное значение — 1 (для индексов — 2). При необходимости таблица транзакций может динамически расти вплоть до MAXTRANS записей (естественно, при наличии достаточного свободного пространства в блоке). Каждая запись транзакции занимает 23 байта в заголовке блока.

Таблицы, организованные в виде кучи Таблицы, организованные в виде кучи, используются приложениями в 99 (если не более) процентах случаев, хотя со временем это может измениться за счет более интенсивного использования таблиц, организованных по индексу, — ведь по таким таблицам Глава теперь тоже можно создавать дополнительные индексы. Таблица, организованная в виде кучи, создается по умолчанию при выполнении оператора CREATE TABLE. Если необходимо создать таблицу другого типа, это надо явно указать в операторе CREATE. "Куча" — классическая структура данных, изучаемая в курсах программирования. Это по сути большая область пространства на диске или в памяти (в случае таблицы базы данных, конечно же, на диске), используемая произвольным образом. Данные размещаются там, где для них найдется место, а не в определенном порядке. Многие полагают, что данные будут получены из таблицы в том же порядке, в каком туда записывались, но при организации в виде кучи это не гарантировано. Фактически гарантировано как раз обратное: строки будут возвращаться в абсолютно непредсказуемом порядке. Это очень легко продемонстрировать. Создадим такую таблицу, чтобы в моей базе данных в блоке помещалась одна полная строка (я использую блоки размером 8 Кбайт). Совсем не обязательно создавать пример с одной строкой в блоке. Я просто хочу продемонстрировать предсказуемую последовательность событий. Такое поведение будет наблюдаться для таблиц любых размеров и в базах данных с любым размером блока:

tkyte@TKYTE816> create t a b l e t 2 (a i n t, 3 b varchar2(4000) default rpad('*',4000,'*'), 4 с varchar2(3000) default rpad('*',3000,'*') 5) 6/ Table created. tkyte@TKYTE816> insert into t (a) values (1);

1 row created. tkyte@TKYTE816> insert into t (a) values (2);

1 row created. tkyte@TKYTE816> insert into t (a) values (3);

1 row created. tkyte@TKYTE816> delete from t where a = 2 ;

1 row deleted. tkyte@TKYTE816> insert into t (a) values (4);

1 row created. tkyte@TKYTE816> select a from t;

A 1 4 Если вы хотите воспроизвести этот пример, измените столбцы В и С в соответствии с размером блока. Например, при использовании блоков размером 2 Кбайт, столбец С Таблицы не нужен, а столбец В должен быть типа VARCHAR2(1500) со стандартным значением 1500 звездочек. Поскольку данные в этой таблице организованы в виде кучи, при появлении свободного пространства оно используется повторно. При полном просмотре данные выдаются в том порядке, в котором обнаруживаются, а не в порядке вставки. Это принципиально важное свойство обычных таблиц базы данных;

в общем случае они представляют собой неупорядоченные наборы данных. Учтите, что для получения подобного результата необязательно использовать оператор DELETE — тех же результатов можно достичь с помощью только операторов INSERT. Если вставить маленькую строку, потом — очень большую, которая не поместится в один блок с маленькой строкой, а затем — опять маленькую строку, при выборке эти строки вполне могут быть получены в порядке "маленькая, маленькая, большая". Они не будут извлекаться в порядке вставки. Сервер Oracle размещает данные там, где они помещаются, а не в порядке выполнения транзакций. Если в результате запроса данные должны выдаваться в порядке вставки, придется добавить столбец, который будет использоваться для упорядочения данных при извлечении. Этот столбец может быть числовым, например, с последовательно увеличивающимися значениями (которые генерируются с помощью объекта SEQUENCE базы данных Oracle). Затем можно будет примерно воспроизвести последовательность вставки, упорядочивая данные по этому столбцу. Это будет лишь приближение, поскольку строка с последовательным номером 55 вполне могла быть зафиксирована в базе данных до строки с последовательным номером 54, поэтому официально она была в базе данных "первой". Поэтому таблицу, организованную в виде кучи, можно рассматривать просто как большой неупорядоченный набор строк. Эти строки будут выдаваться во внешне случайном порядке, зависящем от используемых опций (параллельные запросы, различные режимы оптимизации и т.д.), причем могут выдаваться в разном порядке даже для одного и того же запроса. Никогда не полагайтесь на порядок строк в результатах запроса, если только запрос не включает конструкцию ORDER BY! Что еще важно знать о таблицах, организованных в виде кучи? Вообще-то, синтаксису оператора CREATE TABLE посвящено более 40 страниц в справочном руководстве по языку SQL, предлагаемом корпорацией Oracle, так что различных опций много. Опций настолько много, что разобраться со всеми весьма сложно. Одни только синтаксические ("рельсовые") схемы занимают восемь страниц. Чтобы описать поддерживаемые опции оператора CREATE TABLE, я создам максимально простую таблицу, например:

tkyte@TKYTE816> create table t 2 (x i n t primary key, 3 у date, 4 z clob) 5 / Table created.

Глава Затем с помощью стандартных утилит экспорта и импорта данных (им посвящена глава 8), экспортирую определение таблицы и при импорте потребую показать полный текст оператора создания таблицы:

exp userid=tkyte/tkyte tables=t imp userid=tkyte/tkyte full=y indexfile=t.sql В результате можно обнаружить, что в файле T.SQL содержится максимально подробный оператор создания соответствующей таблицы. Я его немного переформатировал для простоты чтения, но текст взят из файла, сгенерированного утилитой экспорта:

CREATE TABLE "TKYTE"."Т" ("X" NUMBER(*,0), "Y" DATE, "Z" CLOB) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 LOGGING STORAGE(INITIAL 32768 NEXT 32768 MINEXTENTS 1 MAXEXTENTS 4096 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT ) TABLESPACE "TOOLS" LOB ("Z") STORE AS (TABLESPACE "TOOLS" ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10 NOCACHE STORAGE(INITIAL 32768 NEXT 32768 MINEXTENTS 1 MAXEXTENTS 4096 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)) ;

ALTER TABLE "TKYTE"."T" ADD PRIMARY KEY ("X") USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE(INITIAL 32768 NEXT 32768 MINEXTENTS 1 MAXEXTENTS 4096 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT) TABLESPACE "TOOLS" ENABLE ;

В этом тексте показаны многие опции оператора CREATE TABLE. Мне достаточно было только указать типы данных и имена столбцов, а детальную версию сервер Oracle выдал автоматически. Теперь можно отредактировать эту детальную версию, заменив, например, конструкцию ENABLE STORAGE IN ROW конструкцией DISABLE STORAGE IN ROW. Это отключит хранение данных больших объектов в строке вместе со структурированными данными и приведет к созданию отдельного сегмента. Я использую этот прием постоянно, чтобы сэкономить время на обдумывание больших синтаксических схем. Он также позволяет понять, какие опции оператора CREATE TABLE доступны в разных ситуациях.

Таблицы Так я определяю, что же можно синтаксически задать в операторе CREATE TABLE. Я использую этот прием для многих объектов: создаю небольшую тестовую схему, в ней — простейшие объекты, экспортирую ее с помощью параметра OWNER = ЭТА_СХЕМА и выполняю импорт. Просматривая сгенерированный при импорте SQL-файл, я определяю, какие опции доступны. Теперь, зная, как увидеть большинство имеющихся опций оператора CREATE TABLE, попробуем разобраться, какие из них наиболее важны для таблиц, организованных в виде кучи? По моему мнению, это следующие опции. • FREELISTS. Для каждой таблицы выделенные из кучи блоки отслеживаются в списке свободных, FREELIST. Таблица может иметь несколько списков FREELIST. Если предполагается интенсивная вставка данных в таблицу большим количеством пользователей, создание нескольких списков FREELIST может существенно повысить производительность (за счет использования дополнительного пространства). Влияние значения этого параметра на производительность рассматривалось ранее (в разделе "Списки свободных мест"). • PCTFREE. Степень заполнения блока в процессе вставки строк. Как только в блоке осталось менее чем PCTFREE процентов свободного пространства, он уже не просматривается при вставке новых строк. Этот параметр позволяет контролировать перенос строк при последующих изменениях и устанавливается в зависимости от предполагаемого использования таблицы. • PCTUSED. Указывает, насколько пустым должен стать блок, чтобы в него можно было вставлять строки. Новые строки будут вставляться в блок, только если он занят менее чем на PCTUSED процентов. Как и в случае параметра PCTFREE, для правильной установки значения этого параметра необходимо учитывать предполагаемое использование таблицы. • INITRANS. Количество записей в таблице транзакций, первоначально выделяемое в блоке. При слишком низком значении (стандартное значение — 1) могут возникнуть проблемы при одновременном доступе большого количества пользователей. Если блок базы данных почти заполнен и таблицу транзакций нельзя динамически увеличить, сеансы будут ожидать в очереди доступа к блоку, поскольку для каждой из одновременно выполняемых транзакций необходима запись в таблице транзакций. Если предполагается одновременное выполнение большого количества изменений одних и тех же блоков, имеет смысл увеличить значение этого параметра. Примечание: для данных больших объектов, хранящихся в отдельном сегменте вне строки, не используются значения параметров PCTFREE/PCTUSED, установленные для таблицы. Эти блоки больших объектов управляются по-другому. Они всегда заполняются полностью и возвращаются в список свободных, когда полностью пусты. Именно на эти параметры надо обращать особое внимание. Я обнаружил, что остальные параметры хранения теперь просто не слишком важны. Как я уже упоминал ранее в этой главе, необходимо использовать локально управляемые табличные пространства, а для них параметры PCTINCREASE, NEXT и т.п. не используются.

Глава Таблицы, организованные по индексу Таблицы, организованные по индексу (index organized tables — IOTs), — это таблицы, хранящиеся в структуре индекса. Таблица, хранящаяся в куче, организована случайным образом (данные попадают в любое свободное место). В таблице же, организованной по индексу, хранимые данные отсортированы по первичному ключу. С точки зрения приложений, таблицы, организованные по индексу, ничем не отличаются: к ним применяются такие же SQL-операторы, как и для доступа к обычной таблице. Они особенно полезны для информационно-поисковых (information retrieval — IR) систем, хранения пространственных данных и приложений оперативного анализа информации (OLAP). Зачем организовывать таблицу по индексу? Можно задать и прямо противоположный вопрос;

зачем организовывать таблицу в виде кучи? Поскольку все таблицы в реляционной базе данных в любом случае должны иметь первичный ключ, не будет ли организация таблицы в виде кучи непозволительной тратой пространства? При использовании таблицы, организованной в виде кучи, необходимо управлять дисковым пространством как для таблицы, так и индекса по первичному ключу. При использовании таблицы, организованной по индексу, дополнительное пространство для поддержки индекса по первичному ключу не требуется, поскольку индекс — это данные, а данные — это индекс. Проблема в том, что индекс — сложная структура данных, поддержка и управление которой требуют выполнения множества действий. Кучей же управлять сравнительно легко. В некоторых аспектах таблица, организованная в виде кучи, эффективнее таблицы, организованной по индексу. Тем не менее, у таблиц, организованных по индексу, есть ряд преимуществ. Например, однажды я создавал индекс на базе обратного списка (дело было еще до появления interMedia и соответствующих технологий). У меня была таблица документов. Я анализировал документы и находил в них слова. Еще у меня была создана таблица следующего вида:

create table keywords ( word varchar2(50), position int, doc_id int, primary key(word,position,doo_id));

Эта таблица состояла исключительно из столбцов первичного ключа. При этом расходовалось двойное количество пространства;

размеры таблицы и индекса по первичному ключу были сопоставимы (на самом деле индекс по первичному ключу был больше, поскольку в нем хранился идентификатор строки, на которую указывал ключ, а в таблице идентификатор строки отдельно не хранится — он определяется). Я применял к этой таблице SQL-операторы только с конструкцией WHERE, задающей условие для столбца WORD или столбцов WORD и POSITION. Другими словами, я никогда не использовал таблицу, а только индекс таблицы. На таблицу только попусту расходовалось дисковое пространство. Мне нужно было находить все документы, содержащие заданное слово (или одно слово "рядом" с другим и т.п.). Таблица была бесполезна, она просто замедляла работу приложения и удваивала требования к дисковому пространству. Такие таблицы сам бог велел организовывать по индексу.

Pages:     | 1 |   ...   | 3 | 4 || 6 | 7 |   ...   | 24 |





© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.