Номер SCN подтверждения в базе данных Oracle

 записи отмены UNDO в порядке следования номеров SCN в базе данных OracleПродолжаем обсуждать транзакции и согласованность данных в базе Oracle. Механизм взаимодействия транзакций и процесса отмены мы обсудили в этой статье.  В статье "Oracle Redo и Undo" мы указали на то, что связанные списки записей отмены используются для: 1) согласованного чтения, 2) отката изменений и 3) получения SCN подтверждений, которые были «потеряны» из-за отложенной очистки блока. И вот наконец, мы подошли к третьему типу связанных списков, объединяющих записи отмены в порядке следования номеров SCN подтверждений – и эта тема является самой обширной.

Ранее я указывал, что когда выполняется подтверждение транзакции, в соответствующий слот таблицы транзакций записывается текущее значение SCN, а вектор изменения для данного блока данных образует, так называемую, «запись подтверждения» («commit record»).

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

Однако, в версии Oracle 7.3 эта стратегия была изменена, с целью решить проблему «переброски информации» (pinging) в Oracle Parallel Server (OPS) – предшественнике RAC. Если единственное, что было сделано, чтобы показать, что транзакция была подтверждена, заключается в изменении слота в таблице транзакций, то какой-то другой механизм должен «прибраться» за вами, а именно – очистить байты блокировок, записать в столбец Fsc/Scn значение SCN и установить флаг подтверждения C---.

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

Этот «другой механизм» известен под названием отложенная очистка блоков (delayed block cleanout) и почти всегда прекрасно справляется со своей задачей, избавляя от накладных расходов, особенно в системах OLTP с единственным экземпляром. Но в RAC (OPS), если вашему сеансу необходимо выполнить отложенную очистку, вам может потребоваться послать запрос другому экземпляру для получения соответствующих заголовков undo-сегментов, чтобы узнать номера SCN всех неподтвержденных транзакций, изменяющих данный блок. В OPS для этого требуется, чтобы один экземпляр записал ответ на диск (возможно, вытолкнув буфер журнала изменений из кэша), а другой – прочитал его с диска; именно эта ситуация и называется «переброской информации» (pinging). Та же проблема может иметь место и в RAC (но реже, как я полагаю), где операции записи такого типа фиксируются статистикой fusion writes, с той лишь разницей, что при использовании механизма синхронизации кэшей (cache fusion) передача блоков между экземплярами будет осуществляться в основном по сети.

 

Фиксирующая очистка

Из-за застарелой проблемы в OPS, реализация быстрого подтверждения (fast commit) в Oracle была изменена и сделана немного более медленной. Кроме изменения блоков данных, сеанс также конструирует из них список в своей памяти (занимающий не более 10 процентов от размера кэша), которые обходит при подтверждении, обновляет флаг flag в соответствующем элементе ITL, присваивая значение -U--, и устанавливает значение SCN, но не заботится о журналировании
этих изменений. Этот процесс известен, как фиксирующая очистка (commit cleanout). Этого вполне достаточно, чтобы сообщить любому сеансу, который впоследствии будет просматривать блоки, что транзакция была подтверждена и когда она была подтверждена.

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

Итак, общая стратегия подтверждения состоит в том, чтобы выполнить журналируемое (logged) изменение в слоте таблицы транзакций и нежурналируемое (unlogged) – в некоторых блоках данных. Фактически, работа, выполняемая в процессе фиксирующей очистки, нигде не регистрируется – не журналируются не только изменения в элементе списка ITL, операции не воспринимаются даже как чтение буфера и не регистрируются в статистиках. Сценарий cleanout.sql (включен в пакет загружаемых примеров), например, создает таблицу с 500 строками, разбросанными по 500 блокам, и затем изменяет каждую строку. После подтверждения транзакции, статистики с информацией об объеме проделанной работы (в v$sessstat) выглядят так: 

Name                                     Value
----                                     -----
session logical reads                       13
db block gets                                1
consistent gets                             12
db block changes                             1
redo synch writes                            1
commit cleanouts                           500
commit cleanouts successfully completed    500
redo entries                                 1
redo size                                   96

В процессе подтверждения был выполнен обход всех 500 блоков (commit cleanouts 500), но обращения к блокам не были зафиксированы (имеются в виду статистики session logical reads, db block gets, consistent gets); кроме того, несмотря на то, что изменениям подвергся каждый из 500 блоков, было зафиксировано всего одно изменение (db block change) – что, конечно же не так – и создана единственная небольшая запись повторения, описывающая изменение заголовка undo-сегмента (слот в таблице транзакций) и, как следствие, сделана единственная запись в журнал повторений.

Примечание. Технически, список буферов-кандидатов в сеансе для единственной фиксирующей очистки может иметь размер до 10 процентов от размера кэша буферов. Поэтому единственное подтверждение может потребовать значительной доли вычислительной мощности CPU и существенного числа операций с защелками, о чем будет трудно догадаться, если не знать, где искать.

Пример можно сделать чуть более интересным, если добавить выталкивание буфера кэша (alter system flush buffer_cache;) перед подтверждением – просто взгляните, что получилось из этого; изменения в статистиках могут кого-то удивить: 

Name                                  Value
----                                  -----
commit cleanout failures: block lost    100
commit cleanouts                        100

Похоже, что Oracle отказался продолжить работу после 100 попыток выполнить фиксирующую очистку, и даже заглянул в другие 400 блоков. Это – интересный пример работы механизма «статистических ожиданий», который используется для выбора лучшей стратегии выполнения – ему показалось, что фиксирующая очистка работает не особенно хорошо, поэтому он прекратил дальнейшие попытки.

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

Прежде чем детально исследовать, что произойдет с блоками, отсутствующими в кэше, я хочу показать, какую нагрузку оказывает быстрое подтверждение (fast commit). Сценарий core_cleanout_2. sql (доступен в пакете с загружаемыми примерами) – это версия предыдущего теста. Я изменил его так, чтобы следующая инструкция выполнялась три раза: первый – после изменения 500 строк; второй – после изменения контрольной точки (alter system checkpoint;); и третий – после подтверждения:

select
     objd, count(*)
from
     v$bh
where
     dirty = ‘Y’
group by
     objd
order by
     count(*)
;

Эта инструкция просто подсчитывает число буферов в кэше, которые имеют установленный «грязный» бит (dirty bit). Группировка объектов данных по ID была сделана, только чтобы упростить выборку блоков из моей таблицы. Игнорируя некоторые небольшие значения, которые, по всей видимости, обусловлены фоновой деятельностью, я пришел к следующему:

  • после обновления 504 блока в таблице и 212 блоков отмены оказались «грязными»;
  • после изменения контрольной точки  «грязных» блоков не осталось в памяти. Запомните,    что изменение контрольной точки заставляет Oracle скопировать все «грязные» блоки на диск, но не заставляет удалять их из буфера кэша;
  • после фиксирующей очистки «грязными» оставались 500 в таблице и два блока отмены.

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

Примечание. Когда текущая версия блока в буфере кэша не соответствует версии блока на диске, про такой блок говорят, что он «грязный». После того, как dbwr скопирует блок на диск, этот блок станет «чистым», так как будет соответствовать своей копии на диске.

А теперь вернемся немного назад, к ситуации, когда мы обнаружили, что Oracle выполнил 100 неудачных попыток фиксирующей очистки и проигнорировал остальные 400 блоков. Что случится с этими 500 блоками, когда позднее кто-то попытается прочитать их?

 

Отложенная очистка блока

Итак, у нас имеется 500 табличных блоков с подтвержденными изменениями, но в момент выполнения подтверждения блоки отсутствовали в памяти, поэтому у Oracle не было возможности обновить их элементы списков ITL, чтобы показать, что транзакция была подтверждена. Так как же определить, после запроса этих блоков, что транзакция подтверждена (если это потребуется) и когда она была подтверждена?

Если в этот момент выполнить запрос, который произведет сканирование таблицы, в версии 11g статистики будут выглядеть примерно, как показано ниже:

Name                                      Value
----                                      -----
db block gets                                 0
consistent gets                           1,012
consistent gets from cache (fastpath)       501
consistent gets - examination               506
physical reads                              504
db block changes                            500
calls to kcmgrs                             500
redo entries                                500
redo size                                36,000
cleanouts only - consistent read gets       500
immediate (CR) block cleanout applications  500
commit txn count during cleanout            500
cleanout - number of ktugct calls           500
table scan blocks gotten                    500

Я опустил несколько «небольших» значений, не имеющих отношения к обсуждаемой теме, а также несколько статистик, повторяющих значение 500. Ниже перечислены моменты, на которых я хотел бы заострить ваше внимание:

  • Статистика table scan сообщает о 500 блоках, а статистика consistent gets – о 1012: откуда взялись дополнительные 500 (с хвостиком) блоков?
  • Из 1012 операций чтения блоков в согласованном режиме (consistent gets) 506 были выполнены из кэша (consistent gets - examination) – и теперь вы знаете, что одна из причин, вызвавшей их, является поиск блоков в табличном пространстве отмены.
  • Статистика db block gets получила нулевое значение, а статистика db block changes сообщает, что было изменено 500 блоков и создано 500 записей повторения (в среднем по 72 байта на каждую). Это еще один пример, как в случае с фиксирующей очисткой, когда хорошо известные статистики не дают полной картины.
  • • В процессе выполнения теста было выполнено 500 обращений к kcmgrs (Kernel Cache Miscellaneous Get Recent SCN) для получения последнего SCN. Попутно отмечу, чтобы подчеркнуть разницу между реализациями, что когда я выполнил этот тест в 10g, данная статистика не была увеличена.
  • • В листинге имеется несколько статистик, имеющих отношение к «очистке» (cleanouts), включая две, которые как будто бы связаны с подтверждением транзакций: commit txn count during cleanout и cleanout - number of ktugct calls (Kernel Transaction Undo Get Commit Time).

Вот что здесь происходит: когда выполняется чтение блока с диска, Oracle обнаруживает, что имеется список ITL, который, похоже, содержит неподтвержденную транзакцию, и строки, отмеченные этой транзакцией, из-за чего делается вывод о необходимости создать согласованную версию блока. Сеанс читает заголовок undo-сегмента (consistent get - examination), чтобы проверить состояние транзакции (возможно, что перед чтением блока проверяется v$transaction, но это только предположение), обнаруживает, что транзакция подтверждена и можно получить время подтверждения (kcmgrs, kcmgct).

Обнаружив факт подтверждения транзакции, Oracle может скопировать номер SCN подтверждения обратно в элемент списка ITL, установить флаг подтверждения и сбросить байт блокировки. Но это, как мы знаем, является изменением блока данных до его текущей версии (он только что был прочитан с диска, поэтому является актуальной версией, даже при том, что в статистике db block get это никак не зафиксировано), а изменение блока подразумевает создание вектора повторения изменений.

Эта процедура выполняется для каждого отдельного блока во время сканирования таблицы, этим и обусловлено многократное повторение числа, близкого к 500, во многих статистиках – а когда сканирование таблицы завершается, все блоки данных для таблицы оказываются «грязными» и для них создается 500 записей повторения. Обратите внимание, что наш сеанс не показывает изменений в статистике redo synch write (счетчик, который наращивается сеансом, чтобы показать, сколько раз возникала необходимость вызвать механизм записи журнала (log writer) для сохранения журнала на диск). Это говорит о том, что сохранение записей повторения на диске не производится немедленно, но они окажутся там, спустя несколько секунд, или когда будет выполнено подтверждение следующей транзакции, или когда dbwr решит записать несколько «грязных» блоков на диск и обратится к lgwr, для сохранения записей повторения перед сохранением самих блоков.

Примечание. Параллельные запросы – и последовательные операции прямого чтения (direct path reads) в 11g – имеют один интересный побочный эффект с отложенной очисткой блоков. Когда выполняется прямое чтение блока (например, в приватную память, PGA, а не в общедоступную, SGA), Oracle все еще приходится выполнить процедуру создания согласованной версии блока. Это означает необходимость выполнить отложенную очистку блока, причем, скрытно. В этом случае записи повторения не генерируются, и нет необходимости сбрасывать «грязную» копию блока обратно в кэш.

Если взять сценарий core_cleanout_2.sql, выполняющий сканирование таблицы, и на его основе создать параллельную версию (см. сценарий core_cleanout_3.sql, доступный в загружаемом пакете примеров), значения статистик consistent gets, calls to ktugct и других, будут увеличиваться на 500 при каждом сканировании таблицы. Аналогичный эффект возникает, если сделать доступными только для чтения табличные пространства, хранящие объекты, которые ожидают отложенной очистки блока, даже при том, что в версии 11g была реализована концепция минимизации операций с SCN, способная оказывать некоторое влияние на подобное поведение. В версии 11g также появилось понятие «таблицы, доступной только для чтения», но, когда таблица (не табличное пространство) объявляется доступной только для чтения, 11g все еще будет применять и записывать обратно изменения, вызванные отложенной очисткой блока.

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

В примере выше сканирование таблицы выполняется в моменты после подтверждения изменений, поэтому, когда Oracle видит элемент ITL с нарушением, он мог бы прочитать ID транзакции (xid:) из элемента ITL и сказать: «так, теперь прочитаем правильный заголовок undo-сегмента и слот таблицы транзакций, и посмотрим, что произошло».

Но не забывайте, что в таблице транзакций undo-сегмента может быть не более 34 слотов (48 или 96, в зависимости от версии и настроек), и число undo-сегментов в табличном пространстве отмены также ограничено. Если создать совершенно новое табличное пространство отмены в базе данных 11g, переключиться на него и уничтожить прежнее табличное пространство, появится 10 undo-сегментов. Как же тогда будет производиться отложенная очистка блоков, если выполнить 17 000 транзакций (то есть, 50 × 10 × 34) где-то в другом месте в базе данных, прежде чем повторно прочитать таблицу? В конце концов, при таком большом объеме работ, выполненных перед повторным чтением таблицы, слот в таблице транзакций, использованный в момент изменения таблицы, гарантированно будет затерт. Ответ на этот вопрос кому-то покажется удивительным: полученные статистики ничем не будут отличаться от предыдущего теста (см. core_cleanout_4.sql в загружаемом пакете примеров), даже при том, что каждый слот в каждой таблице транзакций внутри базы данных будет перезаписан примерно 50 раз. Так где же взять информацию, необходимую для очистки элементов в списках ITL? Ответ прост (в данном примере): нам не нужно знать, когда наша транзакция была подтверждена; достаточно лишь знать, что она была подтверждена. Как обычно, разобраться нам поможет дамп блока; вот как выглядит ITL в одном из блоков таблицы после очистки (нас интересует транзакция 0x02 – обратите внимание на признак подтвержденного чтения, C-U-):

Itl           Xid                 Uba         Flag Lck      Scn/Fsc
0x01 0x0001.007.00001e7b 0x01800381.0561.13 C--- 0 scn 0x0000.0186e2e6
0x02 0x0003.005.000021a9 0x01800376.06fd.0c C-U- 0 scn 0x0000.01877523

Это пример отложенной очистки блока с использованием подтверждения по верхней границе (upper bound commit). Флаг C указывает, что транзакция была подтверждена и байт блокировки сброшен. Флаг U указывает, что номер SCN подтверждения может быть неверным – транзакция была подтверждена, когда номер SCN имел это значение, но транзакция могла быть подтверждена раньше. Сеансу достаточно просто убедиться, что транзакция подтверждена, перед выполнением запроса, и ему нужно найти самый старый номер SCN, чтобы убедиться в этом, а для этого требуется выполнить чуть больше работы, чем в предыдущем примере, где требовалось найти нужный слот в таблице транзакций.

Происходящее сначала напоминает предыдущую ситуацию: при просмотре блока обнаруживается, что имеется ITL, которая выглядит так, как будто хранит неподтвержденную транзакцию, и можно видеть строки, отмеченные этой транзакцией, поэтому Oracle решает создать согласованную версию блока. Как и прежде, для этого выполняется чтение заголовка undo-сегмента (consistent get - examination), чтобы проверить состояние транзакции. Но в этом случае поле wrap# транзакции оказывается выше ожидаемого (слот был повторно использован примерно 50 раз после выполнения искомой транзакции).

Так как слот транзакции оказался затерт, Oracle делает вывод, что транзакция была подтверждена, чего (в данном случае) вполне достаточно. Но в ITL все еще требуется записать некоторое значение SCN, какое же значение выбрать?

Можно было бы просто использовать значение SCN, найденное в затертом слоте транзакции. Но – почти так же, как выбирается самый старый номер (из недавних), когда требуется повторно использовать элемент ITL в блоке данных – Oracle повторно использует самый старый номер (из недавних) в таблице транзакций внутри undo-сегмента, когда должен начать новую транзакцию. Поэтому лучшей аппроксимацией мог бы служить самый старый номер SCN подтверждения, который удалось найти в таблице транзакций. В действительности имеется вариант немного лучше: когда Oracle повторно использует слот в таблице транзакций, предыдущий номер SCN подтверждения из этого слота копируется в раздел управления транзакциями (transaction control). То есть, когда требуется определить SCN для подтверждения по верхней границе, выполняется поиск соответствующего undo-сегмента, выбирается SCN из раздела управления транзакциями и копируется в ITL.

 

Откат таблицы транзакций

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

Прежде всего, в нашем примере действует единственный сеанс, что делает этот пример не совсем обычным, поэтому взгляните на табл. 3.3, где показано, что происходит, когда имеется два сеанса, действующих одновременно.

Время Сеанс 1 Сеанс 2
t1 Обновить таблицу t1, вытолкнуть буфер, подтвердить  
t2   Цикл с 17 000 подтверждениями транзакций в таблице t2.
t3 Выбрать данные из таблицы t1  

В этом случае результаты никак не изменятся; перед выполнением инструкции select сеансу 1 достаточно будет убедиться, что первоначальная транзакция была подтверждена. Ему не требуется знать точно, когда была подтверждена транзакция, поэтому снова будет использоваться значение SCN из раздела управления транзакциями, как достаточно хорошая аппроксимация.

А что получится, если добавить последовательность событий, как показано в табл. 3.4?

Время Сеанс 1 Сеанс 2
t1 Обновить таблицу t1, вытолкнуть буфер, подтвердить  
t2 Вызвать set transaction read only;  
t3   Цикл с 17 000 подтверждениями транзакций в таблице t2.
t4 Выбрать данные из таблицы t1  

Переведя транзакцию в режим «только для чтения», я, фактически, сымитировал долгоживущий запрос. Когда в момент времени t4 запустится инструкция select, она должна увидеть базу данных в состоянии на момент времени t2. В этой ситуации получаются довольно интересные результаты (см. core_cleanout_4.sql), наиболее значимые из которых приводятся ниже:

session logical reads                                        2,407
consistent gets                                              2,407
consistent gets from cache                                   2,407
consistent gets - examination                                1,900
CR blocks created                                                1
transaction tables consistent reads - undo records applied   1,395
transaction tables consistent read rollbacks                     1

Первый тест показал в статистике session logical reads значение, близкое к 1000, и в статистике consistent gets - examination значение 500. Этот новый тест дал увеличение на 1400 в обеих статистиках и показал то же значение в статистике transaction tables consistent reads - undo records applied. Как видите, был выполнен огромный объем работы, чтобы найти лучшее приближение SCN: было применено 1395 записей отмены для создания единственной согласованной копии заголовка undo-сегмента, чтобы увидеть, как могла выглядеть таблица транзакций в момент подтверждения первой транзакции.

 

Раздел управления транзакциями (TRN CTL::)

Пришло время подробнее остановиться на содержимом раздела управления транзакциями (transaction control) и о том, что происходит в момент запуска транзакции. Ниже приводится пример содержимого раздела в undo-сегменте непосредственно перед запуском новой транзакции: 

TRN CTL:: seq: 0x08f5 chd: 0x000d ctl: 0x0017 inc: 0x00000000 nfb: 0x0001
          mgc: 0xb000 xts: 0x0068 flg: 0x0001 opt: 2147483646 (0x7ffffffe)
          uba: 0x0180120f.08f5.21 scn: 0x0000.018bc704

Обратите внимание на значение scn: в конце и на значение uba: непосредственно перед ним. Эту метку мы уже видели в элементах списка ITL, она расшифровывается как адрес байта отмены (Undo Byte Address), но в действительности это адрес записи отмены. При взгляде на эти два значения возникают законные вопросы: «Что хранится по адресу uba?» и «Откуда взялся этот номер scn?».

Обратите также внимание на chd (думаю, что расшифровывается, как chain head – голова цепочки) и ctl (chain tail – хвост цепочки). Это начало и конец связанного списка, соответственно, следующий и последний слоты в таблице транзакций, используемые в этом undo-сегменте.

Итак, следующий слот (chd) имеет индекс 0x0d. Давайте посмотрим, что в этом слоте хранится в данный момент (я убрал из листинга некоторые детали, чтобы уместить его по ширине книжной страницы): 

index state cflags wrap# uel       scn                dba          nub       cmt
--------------------------------------------------------------------------------
0x0d 9 0x00  0x6a60   0x000b  0x0000.018bc75e  0x0180120d   0x00000001 1305214727

Если теперь запустить новую транзакцию, содержимое раздела управления транзакциями и слота в таблице транзакций изменятся: 

TRN CTL:: seq: 0x08f5 chd: 0x000b ctl: 0x0017 inc: 0x00000000 nfb: 0x0000
mgc: 0xb000 xts: 0x0068 flg: 0x0001 opt: 2147483646 (0x7ffffffe)
uba: 0x0180120f.08f5.24 scn: 0x0000.018bc75e
index state cflags wrap# uel         scn           dba         nub           cmt
--------------------------------------------------------------------------------
0x0d 10 0x80 0x6a61      0x0017 0x0000.018bcd3e 0x0180120f 0x00000001          0

После подтверждения транзакции (если предположить, что в этом undo-сегменте не было никаких других транзакций) их содержимое изменится: 

TRN CTL:: seq: 0x08f5 chd: 0x000b ctl: 0x000d inc: 0x00000000 nfb: 0x0001
mgc: 0xb000 xts: 0x0068 flg: 0x0001 opt: 2147483646 (0x7ffffffe)
uba: 0x0180120f.08f5.24 scn: 0x0000.018bc75e
index state cflags wrap# uel         scn           dba        nub        cmt
--------------------------------------------------------------------------------
0x0d 9 0x00 0x6a61       0xffff 0x0000.018bcd92 0x0180120f 0x00000001 1305278699

Приостановимся ненадолго, чтобы поближе изучить значения chd, ctl и uel, потому что эта часть является самой простой. Если вы помните, в табл. 3.1 столбец uel в таблице транзакций был описан, как указатель на слот, который должен использоваться следующим. Перед запуском транзакции, раздел управления ссылался на слот 0x000d, который в свою очередь ссылался на слот 0x000b. После запуска транзакции поле chd получило значение 0x000b, а поле uel – значение 0x0017.

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

Наконец, после подтверждения поле ctl получает значение 0x000d, а поле uel – значение 0xffff («последний в списке»). То есть, извлекается слот из головы списка, изменяется и превращается в хвост списка. Следовательно, для новой транзакции всегда выбирается самый старый слот (с наименьшим номером SCN) подтвержденной транзакции.

Еще одно небольшое изменение можно заметить в элементе nfb, в разделе управления транзакциями. Это – число свободных блоков (number of free blocks), короткий список блоков в данном undo-сегменте, где имеется место для записей отмены. Перед началом транзакции в этом списке имелся один свободный блок (nfb: 0x0001). Запустив транзакцию, мы заняли этот блок (nfb: 0x0000), а подтвердив ее – вернули освободившийся блок обратно в пул (nfb: 0x0001). Далее приводится дамп пула свободных блоков (только первый элемент), полученный сразу после подтверждения транзакции:

FREE BLOCK POOL::
uba: 0x0180120f.08f5.24 ext: 0x17 spc: 0xe56

Как видите, элемент сообщает объем пространства, доступного в блоке (spc), номер экстента (ext), где находится блок, и адрес последней записи отмены (uba) в этом блоке. Так как в рассматриваемом примере транзакция была очень маленькой и использовала всего одну запись отмены, элемент uba здесь имеет то же значение, что и элемент uba в разделе управления транзакциями: значение uba в разделе управления ссылается на первую запись отмены, созданную транзакцией, а значение uba в пуле свободных блоков – на последнюю, и в данном случае они совпадают.

Вернемся к разделу управления транзакциями и его элементам scn и uba.

Элемент scn получил значение номера SCN подтверждения из использованного слота таблицы транзакций. Именно такое решение позволяет быстро находить «самое старое значение SCN в таблице транзакций» для простых случаев использования подтверждения по верхней границе, когда после проверки слота обнаруживается, что он содержит неверное для рассматриваемой транзакции значение wrap#, и выполняется переход непосредственно к разделу управления транзакциями.

Значение uba замещается значением uba из первой записи отмены новой транзакции. Хотя я уже дважды утверждал, что 0x0180120f.08f5.24 является адресом первой записи отмены новой транзакции, этого пока не видно ни в одном из листингов, которые я привел к настоящему моменту. Мне пришлось получить дамп соответствующего блока отмены, чтобы подтвердить этот факт. Ниже приводится несколько первых строк из этой конкретной записи:

*-----------------------------
* Rec #0x24 slt: 0x0d objn: 99692(0x0001856c) objd: 99692 tblspc: 9(0x00000009)
* Layer: 11 (Row) opc: 1 rci 0x00
Undo type: Regular undo Begin trans Last buffer split: No
Temp Object: No
Tablespace Undo: No
rdba: 0x00000000Ext idx: 0
flg2: 0
*-----------------------------
uba: 0x0180120f.08f5.21 ctl max scn: 0x0000.018bc704 prv tx scn: 0x0000.018bc75e
txn start scn: scn: 0x0000.018bcd3e logon user: 86
prev brb: 25170445 prev bcl: 0

Я уже упоминал ранее, что первая запись отмены для транзакции представляет собой особый случай. Теперь наступило время разобраться в ее особенностях.

 

Первое изменение

Перед началом транзакции раздел управления содержал значения uba 0x0180120f.08f5.21 и scn 0x0000.018bc704. Они появились в записи отмены (три строки снизу) с метками uba: и ctl max scn: – информация из раздела управления транзакциями была скопирована в первую запись следующей транзакции, и затем в ней был изменен указатель, сообщающий, где можно найти данную запись.

Но это еще не все. Предыдущее содержимое слота в таблице транзакций, выбранный для новой транзакции, было скопировано в новую запись отмены. Столбцы scn и dba в слоте имели значения 0x0000.018bc75e и 0x0180120d, а теперь мы видим их в записи отмены с метками prv tx scn: и brb (значение brb в дампе выведено в десятичном, а не в шестнадцатеричном формате). В записи отмены также было сохранено начальное значение scn ( 0x0000.018bcd3e) для новой транзакции – под меткой (включающей странное повторение) label txn start scn: scn:.

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

Представьте, что я прочитал блок некогда в будущем и увидел, что в ITL имеется несколько элементов, которые выглядят как заблокированные транзакцией 0006.00d.00006a5f (то есть: undo-сегмент 6, слот 13, wrap# 27231).

Заглянув в undo-сегмент 6, в слот 13, я обнаружил, что в данный момент столбец wrap# имеет значение 27233 (0x6a61). Так как значение wrap# показывает, что слот уже использовался несколько раз с момента завершения транзакции 0006.00d.00006a5f, я могу сделать вывод, что изменения в строке были подтверждены с более низким номером SCN, чем в данный момент указан в слоте.

Прежде чем двинуться дальше, я могу решить, что этот номер SCN является достаточно хорошей аппроксимацией «верхней границы подтверждения». Если это не так, я могу взять номер SCN из раздела управления транзакциями, который может оказаться номером SCN подтверждения из слота 13 (хотя, статистически, это маловероятно), или номером SCN подтверждения из другой транзакции/слота, подтвержденной после предыдущей транзакции из слота 13. Итак, номер SCN из раздела управления транзакциями является еще одной возможной оценкой «верхней границы».

К настоящему моменту я просто прочитал заголовок undo-сегмента и получил оценку номера SCN подтверждения. Но, если такая оценка недостаточно хороша, можно пойти дальше и потратить еще немного вычислительных ресурсов, чтобы создать согласованную версию таблицы транзакций, достаточно старую, чтобы получить более близкую оценку (или даже точное значение) номера SCN подтверждения.

 

Согласованная версия таблицы транзакций

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

  1. Скопировать заголовок undo-сегмента в память – эта операция увеличит статистику transaction tables consistent read rollbacks.
  2. Использовать значение uba из раздела управления транзакциями, чтобы идентифицировать первую запись отмены транзакции, которая последней изменила раздел управления. Это, как мы уже знаем, самый старый слот, доступный в таблице транзакций на данный момент.
  3. Запись отмены сообщит, к какому слоту в таблице транзакций эта запись отмены должна быть применена (вспомните элемент slt: 0xNN в записи), и номер SCN подтверждения для этого слота. То есть, отмену можно применить к копии в памяти – эта операция увеличит статистику transaction tables consistent reads - undo records applied. В то же время можно прочитать значения uba и scn, которые должны быть записаны в раздел управления транзакциями, и применить их.
  4. В этот момент имеется таблица транзакций и раздел управления транзакциями в состоянии, в каком они находились шаг тому назад. Возможно, что восстановленный номер SCN является «достаточно хорошей» аппроксимацией верхней границы. Возможно, что на этом шаге фактически будет получен слот 13 с требуемым значением wrap# и точным значением SCN. (В данном случае, когда изначально столбец wrap# имеет значение 0x6a61 и требуется получить слот со значением wrap# 0x6a5f, для такой удачи необходимо сначала получить слот со значением wrap# 0x6a60.)
  5. Найденное значение SCN может оказаться недостаточно маленьким, зато удалось создать более старую версию раздела управления транзакциями. Далее можно повторить операции, начиная с шага 2, чтобы получить подходящее значение или исчерпать записи отмены и столкнуться с ошибкой «ORA-01555 snapshot too old» (моментальный снимок слишком стар), если информации в undo-сегменте недостаточно, чтобы заглянуть в прошлое еще дальше.

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


ORA-01555


Если вам неизвестно, что ошибка ORA-01555 переводится как «snapshot too old» (моментальный снимок слишком стар), значит вы ненастоящий администратор. Это, пожалуй, самая известная ошибка в мире Oracle. Она возникает из-за невозможности хранить хронологическую информацию до бесконечности. (Любой, кто пытался создать полную систему аудита для базы данных, чтобы обеспечить хранение хронологической информации хотя бы за семь последних лет, хорошо знаком с этой проблемой – для сохранения всей информации требуется очень много места, и даже при его наличии администратор глубоко в душе надеется, что никто и никогда не попытается обратиться к ней, из-за огромного объема операций ввода/вывода, которые потребуется выполнить для удовлетворения единственного запроса.)

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

Чтобы улучшить ситуацию, в Oracle Corp. реализовали автоматическое управление пространством отмены, позволяющее экземпляру загружать undo-сегменты в память, увеличивать и уменьшать их в размерах, перемещать доступные экстенты из одного сегмента в другой, и даже создавать новые и удалять старые сегменты (последнее выполняется фоновым процессом smon, который запускается только один раз в сутки). Целью автоматического управления пространством отмены является параметр undo_retention, который администраторы используют, чтобы определить, как долго должна храниться хронологическая информация (по умолчанию 15 минут, или 900 секунд). Для поддержки автоматического управления потребовалось ввести таблицу удержания, как показано на рисунке 1 ниже. Эта таблица помогает системе решить, когда безопасно можно удалить экстент из undo-сегмента. В версии Oracle 11.2 дополнительно было введено слежение за минимальным активным значением SCN, что иногда помогает избежать лишних операций по определению верхней границы подтверждения.


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

 

Большие объекты

Следует сказать несколько слов о «больших объектах» (Large Objects, LOB), поскольку для отмены и повторения изменений в таких объектах Oracle использует специальные методы, и эти методы используют приемы обработки транзакций и согласованного чтения. Если значение LOB хранится в строке, в нем нет ничего особенного – это просто еще один столбец. Но, если значение LOB хранится «вне строки» (например, потому что значение конкретного объекта LOB имеет слишком большой объем или объект LOB был объявлен с квалификатором хранения disable storage in row), тогда данные в объектах LOB не обрабатываются стандартными процедурами отмены и повторения (undo и redo).

Самое важное, что нужно помнить при работе с объектами LOB, которые хранятся вне строк, – доступ к ним осуществляется посредством указателей, хранящихся в строках, или через сегмент LOBINDEX, который, хотя и является немного специализированной версией стандартного индекса на основе B-tree, но обслуживается стандартными процедурами отмены и повторения, и потому поддерживает обычные приемы обработки транзакций и согласованного чтения.

Когда изменяется значение объекта LOB, хранящегося вне строки, Oracle создает новую копию этого значения и оставляет старое на месте – прежнее значение может впоследствии использоваться для отмены, а срок его хранения задается определением объекта. Но, при создании новой копии требуется внести два изменения в LOBINDEX: первое, чтобы сообщить местонахождение новой копии, и второе, чтобы определить порядок, в каком будут затираться старые копии. Ограничение на хранение старых копий можно наложить двумя способами: ограничить размер пространства (определить долю в процентах от общего пространства LOB для хранения старых копий) или время хранения (число секунд, в течение которых должны храниться старые копии объектов LOB). В любом случае при частом изменении объектов LOB могут возникать серьезные проблемы, связанные с нехваткой свободного пространства и производительностью.

Соответственно, при использовании объектов LOB необходимо лишь позаботиться о корректной обработке транзакций и согласованного чтения для индекса. Имея корректную версию блока индекса, можно быть уверенными, что он ссылается на требуемую версию значения LOB. Но здесь есть один особый случай. В ходе выполнения длительного запроса, Oracle может обнаружить достаточный объем информации для отмены, чтобы извлечь блок LOBINDEX для получения согласованной версии и затем найти значение LOB, которое оказалось затерто. В такой ситуации возникает особая ошибка ORA-22924 с сообщением «snapshot too old» (моментальный снимок слишком стар), зарезервированная для объектов LOB.

 

В заключение

Эффективную работу транзакций и согласованного чтения в базе данных обеспечивают две основные структуры: список заинтересованных транзакций (Interested Transaction List, ITL), имеющийся в каждом блоке данных и содержащий перечень последних транзакций, выполненных с этим блоком, и таблица транзакций (transaction table), присутствующая в заголовке каждого undo-сегмента и содержащая перечень последних транзакций, выполненных в базе данных.

Элемент ITL хранит ID транзакции (xid:), адрес записи отмены (uba:) и номер SCN подтверждения. Номер SCN сообщает системе, была ли (и когда) подтверждена транзакция. Если номер SCN не указан, тогда по ID транзакции можно определить слот в таблице и порядковый номер, и на основе этой информации проверить состояние транзакции, а также узнать, когда (если) она была подтверждена. Если сеансу потребуется скрыть изменения, выполненные в этой транзакции, он может по адресу записи отмены найти начало цепочки записей, описывающих, как обратить изменения, выполненные транзакцией в этом блоке данных.

Слот в таблице транзакций хранит информацию о состоянии транзакции, адрес последнего блока отмены и (после подтверждения) номер SCN подтверждения. Так как число слотов в таблице транзакций ограничено, они используются повторно, поэтому каждый слот имеет столбец wrap# со счетчиком, в котором подсчитывается число повторных его использований. Этот счетчик является также частью ID транзакции. Если по каким-то причинам транзакцию потребуется откатить, указатель на последний блок отмены позволит Oracle найти последнюю запись отмены, созданную транзакцией, а так как каждая запись отмены имеет ссылку на предыдущую запись, созданную в рамках этой же транзакции, есть возможность обойти все эти записи в обратном порядке и применить их.

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

Всякий раз, когда Oracle следует по списку указателей в undo-сегменте и достигает блока отмены с неверным номером seq:, генерируется ошибка ORA-01555 «snapshot too old» (моментальный снимок слишком стар), потому что искомый блок уже был использован повторно.

Согласованное чтение для объектов LOB реализовано совершенно иначе. В действительности Oracle не изменяет объекты LOB, а просто хранит старые копии, пока не затрет их, но при этом использует стандартный механизм согласованного чтения для LOBINDEX, чтобы обеспечить возможность получения требуемой старой копии. Для объектов LOB предусмотрена специальная ошибка ORA-22924 «snapshot too old» (моментальный снимок слишком стар), которая генерируется, когда индекс доступен для процедуры согласованного чтения, но значение LOB уже было затерто.

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

Транзакции и механизм отмены U...
Транзакции и механизм отмены U... 5668 просмотров Игорь Воронов Tue, 21 Nov 2017, 13:17:28
Транзакция базы данных Oracle
Транзакция базы данных Oracle 2475 просмотров Antoniy Tue, 21 Nov 2017, 13:18:46
Транзакции в Oracle
Транзакции в Oracle 18225 просмотров Ирина Светлова Tue, 21 Nov 2017, 13:18:05
Процесс записи в журнал и на д...
Процесс записи в журнал и на д... 5193 просмотров Antoniy Sat, 20 Jan 2018, 06:16:54
Войдите чтобы комментировать