Транзакции и механизм отмены Undo в базе данных Oracle

Как работает механизм отмены транзакций UNDO в базе данных OracleOracle использует векторы повторения изменений для описания изменений в данных; записи отмены undo – для описания порядка обращения этих изменений; и векторы повторения изменений (снова) – для описания записей отмены; а затем – как применяются изменения в момент подтверждения (исключая оптимизации, добавленные в Oracle Database 10g). Ранее мы уже говорили немного о способах использования записей отмены для «сокрытия» изменений до определенного момента и их отката, если вдруг будет принято решение, что эти изменения не нужны. Механизм повторения действует по принципу «записал и забыл», тогда как механизм отмены (undo) создает разные связанные списки из своих записей, чтобы использовать их в разных ситуациях.

В этой статье мы исследуем механизмы, которые применяются для создания связанных списков записей отмены и, что особенно важно, узнаем, как реализация находит концы этих списков. Здесь мы также познакомимся с третьим типом связанных списков – связанными списками, обеспечивающим доступ к истории подтверждений (и я расскажу, как эти списки используются механизмом отложенной обработки).

Мы будем рассматривать таблицы транзакций, которые хранятся в каждом заголовке undo-сегмента и закрепляют множество связанных списков, а так же списки заинтересованных транзакций (Interested Transaction List, ITL), которые Oracle хранит для каждого отдельного блока данных (и индексов). Затем мы поближе познакомимся с заголовком undo-сегмента и исследуем разделы управления таблицами транзакций (в дальнейшем мы будем называть их разделами управления транзакциями) которые в Oracle используются как точки привязки окончательно сформированных связанных списков.

В новых заметках мы коротко познакомимся с «большими объектами» (Large Objects, LOB) и особенностями выполнения операций повторения, отмены, согласованного чтения и подтверждения транзакций с такими объектами, то есть с данными, которые хранятся «вне строк».

 

Разрешение конфликтов

Представьте, что имеется система, где всего два пользователя, Вы и я, и мы с вами постоянно изменяем и запрашиваем данные небольшими порциями.

Когда вы выполняете транзакцию, а я просто запрашиваю данные, я не должен видеть ваши изменения, пока вы не сообщите (выполнив команду commit;), что они доступны. Но, даже когда вы подтвердите свою транзакцию, момент, когда я увижу изменения, зависит от уровня изоляции моей транзакции (см. раздел «Уровни изоляции» в этом блоге) и от характера выполняемой мною работы. То есть, с точки зрения механики, у меня должен быть эффективный способ определения (и игнорирования) изменений, которые еще не были подтверждены, а так же изменений, которые были подтверждены настолько недавно, что я не должен их видеть. Кроме того, я должен помнить, что для запросов, выполняющихся длительное время, понятие «недавно» не всегда означает «вот только что», поэтому мне потребуется немало потрудиться, чтобы узнать, когда ваша транзакция была подтверждена.

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

Прежде всего, для координации наших с вами действий необходим некоторый «координационный узел», в котором будут сосредоточены изменения. Так как в данном сценарии источником изменений являетесь вы, вы и должны предоставить такой координационный узел, или нет, два узла – первый будет служить входом в специальный раздел базы данных со ссылкой на транзакцию, а второй – входом в каждый измененный блок данных или индексов. Сначала рассмотрим ссылку на транзакцию.

 

Транзакции и механизм отмены в базе данных Oracle

Создавая базу данных, вам необходимо создать табличное пространство отмены (а при использовании RAC необходимо будет создать такое табличное пространство для каждого экземпляра, имеющего доступ к базе данных). Если вы не используете старый механизм управления откатом вручную, Oracle автоматически создаст несколько undo-сегментов в этом табличном пространстве и будет автоматически добавлять или удалять сегменты в ходе изменений в базе данных.

Управление транзакциями осуществляется с помощью undo-сегментов. Заголовок сегмента, который (для undo-сегментов) является первым блоком сегмента, содержит множество стандартных структур, которые можно встретить также в заголовках сегментов других типов – например, карта экстента (extent map) и заголовок управления экстентом (extent control header), – но здесь же имеется несколько узкоспециализированных структур (см. рис. 1), в частности, таблица транзакций (transaction table, TRN TBL:, список, идентифицирующий последние транзакции) и раздел управления таблицей транзакций (transaction table control section, TRN CTL::, массив, описывающий состояние и содержимое таблицы транзакций).

В следующем листинге приводится выдержка из таблицы транзакций, в которой я оставил только несколько первых и последних строк и скрыл некоторые столбцы, которые не рассматриваются в данной дискуссии. Эта выдержка включает строку (index = 0x02), предстваляющую активную транзакцию.

TRN TBL:
index state cflags wrap# uel scn dba nub cmt
--------------------------------------------------------------------------------
0x00 9 0x00 0x2013 0x001b 0x0000.016f1fc1 0x0180083e 0x00000001 1302762364
0x01 9 0x00 0x2014 0x001a 0x0000.016f1f54 0x0180083e 0x00000001 1302762364
0x02 10 0x80 0x2013 0x0002 0x0000.016f20fc 0x0180083e 0x00000001 0
0x03 9 0x00 0x200c 0x001c 0x0000.016f20d8 0x0180083e 0x00000001 1302762364
...
0x20 9 0x00 0x200f 0x001f 0x0000.016f1c75 0x0180083f 0x00000001 1302762364
0x21 9 0x00 0x2013 0x0010 0x0000.016f1e0c 0x0180083f 0x00000001 1302762364

Эта выдержка получена из блока размером 8 Кбайт, использующего автоматическое управление отменой, в системе Oracle Database 11g, а ограниченность размера блока свидетельствует, что таблица транзакций хранит всего 34 строки. (В более ранних версиях Oracle в сегментах с автоматическим управлением отменой хранится 48 записей, а в сегментах с ручным управлением – не имеющих таблицы удержания (retention map) – хранится 96 записей).

Рис. 1. Сравнение основных компонентов заголовков
сегментов разных типов

Из-за ограниченного числа записей в таблице транзакций и ограниченного числа undo-сегментов в табличном пространстве отмены, можно хранить информацию только о небольшом числе недавних транзакций и приходится повторно использовать записи в таблице транзакций. С повторным использованием напрямую связан столбец wrap#; каждый раз, когда запись используется повторно, значение столбца wrap# в ней увеличивается на единицу.

Примечание. Иногда мне задают вопрос: «Сбрасывается ли значение столбца wrap#, когда происходит перезапуск экземпляра?». Ответ на этот вопрос: нет. Здесь действует общий принцип: никакие счетчики, хранящиеся в базе данных, не сбрасываются при перезапуске экземпляра. Не забывайте, что каждый слот в каждом undo-сегменте имеет собственный счетчик wrap#, поэтому, чтобы сбросить их все, на каждом запуске потребовалось бы выполнить массу работы.

 

Начало и конец транзакции в СУБД Oracle

Когда сеанс запускает транзакцию, он выбирает undo-сегмент, извлекает запись из таблицы транзакций, увеличивает значение wrap#, изменяет значение состояния state на «активное» (число 10) и изменяет некоторые другие столбцы. Так как все это вызывает изменения в блоке базы данных, попутно создается вектор повторения изменений (с кодом OP: 5.2), который в конечном итоге записывается в файл журнала повторений; тем самым остальным сообщается, что сеанс имеет активную транзакцию.

Аналогично, когда транзакция заканчивается (обычно командой commit;), сеанс устанавливает состояние state «свободно» (число 9) и обновляет несколько других столбцов в записи – в частности, записывает текущее число SCN в столбец scn. И снова, так как изменения происходят в блоке базы данных, создается вектор повторения изменений (с кодом OP: 5.4), который затем записывается в файл журнала повторений. Это особый момент, так как именно в этот «момент» сеанс защищает подтвержденные изменения вызовом механизма записи в журнал (lgwr) для записи текущего буфера журнала на диск и затем ожидает, пока механизм записи не подтвердит, что запись закончена. После этого в вашем распоряжении появляется хранимая запись с информацией о транзакции, или, выражаясь языком требований ACID, обеспечено ее надежное хранение.

Важно. В комментариях в Интернете и в документации к Oracle вам часто будет попадаться фраза: «создание записи подтверждения». Вообще говоря, такой операции не существует. Подтверждая свои изменения, вы изменяете блок в базе данных, в частности – заголовок undo-сегмента, где хранится таблица транзакций с использованной записью, а это влечет за собой создание вектора повторения изменений (который по историческим причинам называют записью повторения изменений) и его копирование в буфер журнала повторений. Именно этот вектор изменений (неформально) называют «записью подтверждения»; но это ваш сеанс (а не механизм записи в журнал) генерирует его и записывает в буфер. Единственной необычной особенностью, связанной с «записью подтверждения» является тот факт, что после ее копирования в буфер сеанс вызывает механизм сохранения текущего содержимого буфера журнала на диск и ждет, пока операция сохранения не завершится. 

Транзакция определяется соответствующей записью в таблице транзакций и получает соответствующий идентификационный номер (ID), сконструированный из номера undo-сегмента, числа index в записи и последнего значения wrap# в этой записи, то есть, ID транзакции 0x0009.002.00002013 можно перевести так: undo-сегмент 9, запись 2, wrap# 0x2013 (десятичное число 8211). Чтобы получить undo-сегмент и узнать местоположение его заголовка, можно выполнить запрос к представлению dba_rollback_segs по полю segment_id.

Взаимодействие транзакций и отмены UNDO в базе данных Oracle

Идентификационный номер (ID) транзакции используется в нескольких разных местах. Из них наиболее известными являются динамические представления v$transaction и v$lock. Ниже приводятся примеры, полученные в экземпляре, где не производилось никаких других действий, поэтому, выполняя запросы, я знал, что каждый вернет только одну строку, соответствующую запущенной мной транзакции:

select xidusn, xidslot, xidsqn from v$transaction;
    XIDUSN    XIDSLOT     XIDSQN
---------- ---------- ----------
         9          2       8211
select trunc(id1/65536) usn, mod(id1,65536) slot, id2 wrap, lmode
from V$lock where type = ‘TX’;
       USN       SLOT       WRAP      LMODE
---------- ---------- ---------- ----------
         9          2       8211          6

Обратите внимание, что «режим блокировки» (поле LMODE) для этой транзакции равен 6 (монопольный, или X (eXclusive), режим). Пока моя транзакция остается активной, никто другой не сможет изменить эту запись в таблице транзакций, хотя другие сеансы могут попробовать получить доступ к ней в режиме 4 (разделяемый, или S (Share) режим), чтобы поймать момент подтверждения (или отката) транзакции. Обратите также внимание, что «записи» (как я их называю) в таблице транзакций в представлении называются «слотами» (SLOT), поэтому далее я буду использовать это название.

 

Таблица транзакций Oracle

В таблице 1 ниже приводятся описания столбцов таблицы транзакций, которые вы видели в примере выше.

Столбец Описание
index Идентифицирует строку в таблице транзакций и используется как часть ID транзакции. Часто значение этого столбца называют номером слота в таблице транзакций. (Это значение фактически не хранится в блоке – оно генерируется реализацией в момент вывода дампа.)
state Состояние записи: 9 – неактивное, 10 – активное.
cflags Битовый флаг, отражающий состояние транзакции, которая описывается данным слотом: 0x0 – нет транзакции, 0x10 – транзакция прервана, 0x80 – активная транзакция. (0x90 – транзакция прервана и подлежит откату).
wrap# Счетчик, определяющий, сколько раз использовался данный слот. Часть ID транзакции.
uel Указатель на следующий слот в таблице транзакций, который должен использоваться после того, как этот слот станет активным. В новом сегменте это поле будет получать значения, следующие по порядку, но по мере увеличения числа выполненных транзакций будет образовываться все более запутанный связанный список слотов.
scn Номер SCN для подтвержденной транзакции. (Так как команда rollback завершается подтверждением, она также увеличивает номер SCN). В большинстве версий Oracle значение этого столбца используется также как стартовый номер SCN, когда транзакция активна, но, по странному стечению обстоятельств, моя версия 10.2.0.3 выводит в этом поле ноль для активных транзакций.
dba Адрес блока данных (Data Block Address) последнего блока отмены, который используется транзакцией для сохранения записи отмены. Помогает Oracle (особенно при восстановлении после сбоев)найти последнюю запись отмены, сгенерированную транзакцией, откуда следует начать процедуру отката.
nub Число блоков отмены (Number of Undo Blocks), используемых этой транзакцией. (В процессе отката транзакции можно наблюдать, как это число уменьшается.)
cmt Время подтверждения (Commit Time) с точностью до ближайшей секунды. Соответствует числу секунд, прошедших с полуночи (UTC) 1 января 1970 года. Для активных транзакций равно нулю. Так как, по всей видимости, это 32-разрядное целое число, мне кажется, что многие системы столкнутся с проблемами в январе 2038 года, если будут интерпретировать его как целое со знаком, и в феврале 2106, если будут интерпретировать его как целое без знака.

В действительности не требуется выводить дамп всего блока, чтобы увидеть содержимое таблицы транзакций, потому что она доступна в виде одной из структур x$: x$ktuxe. Это одна из самых странных структур в Oracle, потому что запрос к этой структуре фактически заставляет Oracle обойти все заголовки блоков во всех undo-сегментах, имеющихся в базе данных. В следующем примере используется иное форматирование, к тому же отсутствует столбец cmt (время подтверждения транзакции). 

select
indx,
ktuxesta,
ktuxecfl,
ktuxesqn wrap#,
ktuxescnw scnW,
ktuxescnb scnB,
ktuxerdbf dba_file,
ktuxerdbb dba_block,
ktuxesiz nub
from
x$ktuxe
where
ktuxeusn = 9
and ktuxeslt <= 5
;
INDX  KTUXESTA    KTUXECFL     WRAP#   SCNW       SCNB   DBA_FILE  DBA_BLOCK    NUB
----  ----------  ----------  ------  -----  ---------  ---------  ---------  -----
   0  INACTIVE    NONE          8211      0   24059841          6       2110      1
   1  INACTIVE    NONE          8212      0   24059732          6       2110      1
   2  ACTIVE      NONE          8211      0   24060156          6       2110      1
   3  INACTIVE    NONE          8204      0   24060120          6       2110      1
   4  INACTIVE    NONE          8212      0   24059364          6       2111      1
   5  INACTIVE    NONE          8212      0   24059497          6       2110      1 

Итак, таблица транзакций содержит все необходимое для получения ID транзакции, а именно:

  • физический адрес местоположения в базе данных;
  • признак активности транзакции;
  • номер SCN для подтвержденных транзакций;
  • информация о том, где искать самую последнюю запись отмены, сгенерированную транзакцией;
  • объем информации, описывающей отмену, сгенерированной транзакцией.

Теперь мы имеем доступ к важной информации о самых последних N × 34 транзакциях (где N – число undo-сегментов, доступных процессу-клиенту, 34 – число слотов в таблице транзакций на один undo сегмент в версии 11g, и с учетом, что транзакции следуют наиболее типичному шаблону), выполнявшихся в базе данных.

В частности, если логика выполнения потребует откатить транзакцию, или если сеанс был прерван и smon (system monitor – системный монитор) должен откатить транзакцию, или если экземпляр завершил работу аварийно и в процессе восстановления smon должен откатить все транзакции, которые были активными на момент сбоя, можно без труда выбрать любую активную транзакцию (state = 10) и найти последний блок отмены (dba). После этого можно организовать обход цепочки блоков отмены в обратном порядке и применить все записи отмены, благодаря тому, что  каждая запись отмены указывает на предыдущую. Между прочим, многие упускают из виду, что когда Oracle применит все соответствующие записи отмены, на последнем этапе выполняется обновление слота в таблице транзакций, чтобы показать, что транзакция завершена, то есть, подтверждена.

Примечание. Есть возможность установить именованную точку сохранения в середине транзакции и затем выполнить откат до этой точки (rollback to savepoint X). В этом случае сеанс будет хранить текущие точки отмены в памяти сеанса, вместе с адресами последних записей отмены, созданных перед установкой точки сохранения. Это позволит сеансу применить записи отмены в обратном порядке и остановиться в нужном месте. Установка точки сохранения внутри транзакции имеет интересный (но недокументированный) побочный эффект: похоже, что это приводит к отмене некоторых оптимизаций, используемых при обработке массивов, которые иногда используются при конструировании записей отмены.

 

Обзор блока отмены UNDO

Сейчас я хочу предложить взглянуть на небольшую выдержку из блока отмены, потому что в ней имеется информация о «владельце» блока, которая необходима для полноты картины. Ниже приводится начало дампа с содержимым блока отмены, где хранится каталог записей (record directory) и небольшие отрывки из первой и последней записей:

**************************************************************************
UNDO BLK:
xid: 0x0008.029.00002068 seq: 0x97a cnt: 0xc irb: 0xc icl: 0x0 flg: 0x0000
Rec Offset Rec Offset Rec Offset Rec Offset Rec Offset
--------------------------------------------------------------------------
0x01 0x1f9c 0x02 0x1f4c 0x03 0x1ebc 0x04 0x1e88 0x05 0x1e2c
0x06 0x1db8 0x07 0x1d40 0x08 0x1cec 0x09 0x1c84 0x0a 0x1c28
0x0b 0x1bbc 0x0c 0x1b60
*-----------------------------
* Rec #0x1 slt: 0x17 objn: 2(0x00000002) objd: 4294967295 tblspc:
12(0x0000000c)
* Layer: 22 (Tablespace Bitmapped file) opc: 3 rci 0x00
Undo type: Regular undo Begin trans Last buffer split: No
...
*-----------------------------
* Rec #0xc slt: 0x29 objn: 45756(0x0000b2bc) objd: 45756 tblspc: 12(0x0000000c)
* Layer: 11 (Row) opc: 1 rci 0x0b
Undo type: Regular undo Last buffer split: No
Temp Object: No

Если не углубляться в детали, блок отмены имеет много общего с обычным блоком данных. Он имеет заголовок с некоторой управляющей информацией и метаданные – каталог строк (row directory), в котором перечислены местоположения элементов внутри блока, кучу элементов (в данном случае, записей отмены), располагающихся от конца блока и вверх, и свободное пространство в середине. Однако есть одно важное отличие строк таблицы от записей отмены. Записи отмены не изменяются (за исключением одного особого случая), поэтому они всегда остаются на своих местах после сохранения в блоке. Строки таблиц, напротив, могут копироваться в область свободного пространства при изменении, в результате чего образуются лес запутанных указателей и «дырок» (временно) в блока (см. рис. 2).

Примечание. Существует один особый случай, когда записи отмены могут изменяться, правда изменяется лишь однобайтовый флаг, то есть, размер записи не изменяется и потому ее не требуется копировать при изменении. Однако, изменение единственного байта порождает запись отмены с размером в несколько десятков байтов. Изменения происходят, когда откатывая транзакцию (или выполняя откат до точки сохранения) сеанс использует запись отмены, – в этот момент он присваивает флагу в записи значение User Undo Applied. Увидеть это можно в статистике rollback changes - undo records applied.

Взгляните еще раз на первые строки дампа, элемент xid: (ID транзакции) имеет значение 0x0008.029.00002068. Это означает, что сегментом с номером 8 (0x0008) данного блока отмены «владеет» транзакция, занимающая слот 41 (0x029) в таблице транзакций (так как номер слота больше 34, можно сделать вывод, что дамп получен в версии Oracle ниже 11g), который использован уже в 8296 раз (0x00002068). Из «числа перевоплощений» (incarnation number, seq: 0x97a) следует, что сам блок отмены очищался (или обновлялся) и повторно использовался уже 2426 раз.

Рис. 2. Схематическое сравнение блока отмены
и обычного блока данных

Примечание. Когда блок отмены используется повторно, его прежнее содержимое не представляет интереса, поэтому Oracle не читает его содержимое с диска перед повторным использованием, а просто выделяет новый буфер и форматирует его под хранение нового пустого блока отмены. Этот процесс обычно называют «обновлением» блока. Однако, если включено ретроспективное резервирование базы данных Flashback Database, Oracle будет копировать старую версию блока в журнал ретроспекции (flashback log), то есть, будет читать блок из файла перед обновлением. Этого рода деятельность можно увидеть в статистике physical reads for flashback new. Данный механизм не ограничивается блоками отмены – тот же эффект можно наблюдать при вставке новых строк в только что усеченную таблицу, например, но это наиболее типичная причина, объясняющая вклад в упомянутую статистику, когда включается механизм ретроспективного резервирования.

Однако, первая строка в записи #0x1, где можно видеть текст slt: 0x17, не соответствует первой строке последней записи (#0xC) в блоке, где имеется текст slt: 0x29. Это означает, что первая запись была вставлена в данный блок отмены транзакцией, занимающей слот 23 (0x17) в таблице транзакций, а последняя запись – транзакцией, занимающей слот 41 (0x29), что вполне ожидаемо, поскольку транзакция из этого слота является «владельцем» блока.

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

Если на момент подтверждения транзакции в блоке остается достаточно свободной памяти (примерно 400 байт, когда я проверял в последний раз), блок будет добавлен в короткий список в заголовке undo-сегмента, который называют пулом свободных блоков (free block pool). В этом случае следующая транзакция, запущенная в этом же undo-сегменте, получит блок из пула и будет использовать оставшееся пространство. То есть, активные транзакции не могут одновременно писать в один и тот же блок отмены, но несколько последовательных транзакций могут использовать один и тот же блок.

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

Примечание. Некоторые испытывают обеспокоенность по поводу увеличения значения user rollbacks в своих системах, когда они просматривают статистику Rollback per transaction %: с помощью инструмента Automatic Workload Repository (AWR) или Statspack. Не волнуйтесь, пока не познакомитесь со статистиками активности экземпляра transaction rollbacks и rollback changes - undo records applied. Вполне возможно, что вы используете один из тех серверов веб-приложений, которые выполняют избыточные команды rollback; после каждого запроса к базе данных. В результате получится большое число user rollbacks откатов, которые не превращаются в откаты транзакций transaction rollbacks и не выполняют никакой работы.

Итак, теперь вы знаете, как запускается и завершается транзакция, и как она откатывается – по доброй воле или в результате краха системы. В управлении транзакциями имеется масса тонкостей, которые мы могли бы исследовать, но к настоящему моменты мы пока охватили лишь деятельность, связанную с таблицей транзакций. Теперь пришло время взглянуть на работу механизма отмены с другой стороны и обратить внимание на блоки данных и структуру ITL, которую транзакции используют для координации изменений в блоках.

 

Вас заинтересует / Intresting for you:

Номер SCN подтверждения в базе...
Номер SCN подтверждения в базе... 6202 просмотров Игорь Воронов Tue, 21 Nov 2017, 13:17:28
Oracle и непроцедурный доступ ...
Oracle и непроцедурный доступ ... 8510 просмотров Antoni Tue, 21 Nov 2017, 13:32:50
Видеокурс по администрированию...
Видеокурс по администрированию... 10719 просмотров Илья Дергунов Mon, 14 May 2018, 05:08:47
Транзакция базы данных Oracle
Транзакция базы данных Oracle 2462 просмотров Antoniy Tue, 21 Nov 2017, 13:18:46
Войдите чтобы комментировать