Блоки данных и механизм отмены Undo в базе данных Oracle

Как изменяются блоки данных во время транзакций отмены Undo в базе данных OracleВсякий раз, когда сеанс просматривает блок данных в СУБД Oracle, он должен гарантировать, что вы получите соответствующую версию данных. То есть, с точки зрения внешнего наблюдателя, сеанс не должен видеть неподтвержденные данные, или данные, измененные и подтвержденные после того, как ваш запрос уже начал выполнение (или инструкция DML и даже транзакция – в зависимости от уровня изоляции). Это называют чтением согласованной версии данных.

Примечание. Легко забыть, что согласованное чтение является обязательным атрибутом изменяемых данных. Если сеанс изменяет данные в блоке, он должен иметь возможность видеть их с двух разных сторон – он должен видеть текущую версию данных, потому что в данный момент является единственным, наделенным правом доступа к ней, и он должен видеть согласованную версию данных, потому что при наличии существенных различий между двумя версиями, сеансу может потребоваться подождать, перезапустить текущую инструкцию или даже прервать работу, сгенерировав ошибку (обычно ORA-08177: can’t serialize access for this transaction или на русском ORA-08177: невозможно сериализовать доступ для этой транзакции).

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

 

Подготовка полигона

Начнем с примера запроса данных. Представьте следующую последовательность событий в многопользовательском окружении, где кроме вашего работают еще три сеанса, а таблица была создана и заполнена данными следующими инструкциями SQL (см. core_03_ct.sql в загружаемом пакете примеров, который можно получить на сайте издательства Apress [www.apress.com]): 

create table t1(id number, n1 number);
insert into t1 values(1,1);
insert into t1 values(2,2);
insert into t1 values(3,3);
commit;

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

Block header dump: 0x00c0070a
Object id on Block? Y
seg/obj: 0x18317 csc: 0x00.1731c44 itc: 2 flg: O typ: 1 - DATA
fsl: 0 fnx: 0x0 ver: 0x01
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0001.001.00001de4 0x01802ec8.0543.05 --U- 3 fsc 0x0000.01731c46
0x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000
bdba: 0x00c0070a
data_block_dump,data header at 0xc90225c
===============
tsiz: 0x1fa0
hsiz: 0x18
pbl: 0x0c90225c
76543210
flag=--------
ntab=1
nrow=3
frre=-1
fsbo=0x18
fseo=0x1f85
avsp=0x1f6d
tosp=0x1f6d
0xe:pti[0] nrow=3 offs=0
0x12:pri[0] offs=0x1f97
0x14:pri[1] offs=0x1f8e
0x16:pri[2] offs=0x1f85
block_row_dump:
tab 0, row 0, @0x1f97
tl: 9 fb: --H-FL-- lb: 0x1 cc: 2
col 0: [ 2] c1 02
col 1: [ 2] c1 02
tab 0, row 1, @0x1f8e
tl: 9 fb: --H-FL-- lb: 0x1 cc: 2
col 0: [ 2] c1 03
col 1: [ 2] c1 03
tab 0, row 2, @0x1f85
tl: 9 fb: --H-FL-- lb: 0x1 cc: 2
col 0: [ 2] c1 04
col 1: [ 2] c1 04
end_of_block_dump

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

 

Список заинтересованных транзакций

В таблице ниже перечислены все элементы в списке ITL.

Столбец Описание
Itl Индекс в списке. Это число физически не хранится в блоке – оно генерируется реализацией в момент вывода дампа. Используется в байте блокировки (lock byte, lb:) строки и показывает, какая транзакция заблокировала строку.
Xid ID последней транзакции, изменившей этот блок. Это значение создается по шаблону: номер_undo-сегмента.номер_слота.порядковый_номер_отмены.
Uba Адрес записи отмены – включая номер перевоплощения – которая была сгенерирована последней в блоке отмены этой транзакцией для данного блока. Адрес имеет формат: абсолютный_адрес_блока.номер_перевоплощения.номер_записи_в_блоке. (Символ «b» в имени метки означает «byte» или «block», но ни одна из этих интерпретаций не является абсолютно точной.)
Flag

Набор битовых флагов для точной идентификации состояния транзакции: ----: активная (то есть, «никогда не завершалась»).

--U-: подтверждение по верхней границе (также устанавливается при выполнении «быстрого подтверждения»).

C---: транзакция подтверждена и все блокировки сброшены (всем соответствующим байтам блокировки присвоены нулевые значения).

-B--: может относиться к рекурсивным транзакциям, вызывающим деление блоков индексов (index block splits). Мне приходилось видеть комментарии, в которых утверждалось, что когда установлен этот флаг, столбец UBA будет указывать на запись, хранящую предыдущее содержимое элемента ITL, но мне не удалось найти подтверждений этому.

---T: Мне приходилось видеть комментарии, в которых утверждалось, что этот флаг означает, что транзакция была активной на момент очистки блока, но мне не удалось найти подтверждений этому.

Lck Число строк, заблокированных этой транзакцией в данном блоке.
Scn/Fsc Зависит от значения Flag, SCN подтверждения или объем свободного пространства в байтах, который станет доступен при подтверждении транзакции (кредит свободного пространства – Free Space Credit).

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

Размер списка ITL ограничивается параметром maxtrans (по крайней мере, так было в ранних версиях Oracle), который имеет синтаксический предел 255, а физический предел определяется размером блока данных объекта. К сожалению, синтаксический предел игнорируется в версиях Oracle 10g и выше; и для блоков размером 8 Кбайт физический предел составляет 169 элементов.

Примечание. Параметр initrans действует порой несколько необычно. В случае с индексами, значение initrans применяется только к листовым блокам (leaf blocks) – каждый блок ветвления (branch block) (включая и корневой блок) получает список ITL с единственным элементом, который используется только для деления блоков. Первый элемент списка ITL в листовых блоках резервируется для их деления, при этом существует один особый случай: когда новый индекс создается в пустой (или почти пустой) таблице, единственный блок индекса будет одновременно играть роль корневого блока (блок ветвления) и листового блока, поэтому он получит список ITL с двумя элементами – один, как блок ветвления, и второй, как листовой блок, необходимый для выполнения других действий, отличных от деления блока. Исторически, параметру initrans по умолчанию присваивалось значение 1 для таблиц, но в последних версиях Oracle стало присваиваться значение 2, даже при том, что словарь данных все еще сообщает о значении по умолчанию 1. (А если загрузить блоки, используя метод прямой загрузки (direct path loads), можно обнаружить, что они изначально получают списки ITL с тремя элементами.)

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

Примечание. Существует два других числа SCN, хранимых в фиксированных местоположениях в каждом блоке данных: «номер очистки блока» (cleanout SCN, в дампе обозначается меткой csc:) – число SCN, отражающее число операций и изменяющееся операцией полной очистки блока (см. раздел «Отложенная очистка блока» ниже в этой главе), и «номер последнего изменения» (в дампе обозначается меткой scn:), который изменяется при изменении содержимого блока и связан с дополнительным байтом (с меткой seq:), где хранится число изменений блока с данным значением SCN (если байт seq: достигает значения 254, в следующий раз ему будет присвоено значение 1 и будет увеличено на единицу значение SCN в экземпляре).

Итак, в этом блоке мы видим короткий список последних транзакций. В действительности данный блок еще настолько новый, что один из элементов списка ITL пока не использовался – элемент с индексом 0x02 пока пустой. Но в элементе с индексом 0x01 можно видеть, что совсем недавно блок изменялся транзакцией 1.1.1de4 (undo-сегмент 1, слот 1, номер 7652). Она была подтверждена с номером SCN 0x01731c46, но блок еще не был очищен, потому что имеет флаг --U- подтверждения по верхней границе (ниже мы будем рассматривать разные эффекты, проявляющиеся при подтверждении транзакции), а счетчик блокировок сообщает, что заблокировано три строки. (Чуть ниже в дампе можно видеть, что все три строки в блоке имеют значение lb: 0x01 – то есть, эти строки заблокированы транзакцией в элементе 0x01 списка ITL.) Наконец, по значению uba видно, что если перейти к записи 5 в блоке 11976, в файле 6 (который должен иметь порядковый номер 0x0543), можно найти информацию, описывающую, как отменить последние изменения, выполненные транзакцией.

Я не буду приводить полный дамп соответствующей записи отмены, а только сообщу, что запись 5 из блока отмены говорит следующее: «очистить слот (строку) 0x02 в блоке таблицы и изменить uba в элементе 1 списка ITL на 0x01802ec8.0543.04 (то есть, чтобы он указывал на запись 0x04 в текущем блоке отмены)». Запись 4 говорит: «очистить слот (строку) 0x01 в блоке таблицы и изменить uba в элементе 1 списка на 0x01802ec8.0543.03». И, наконец, запись 3 говорит: «очистить слот (строку) 0x01 в блоке таблицы и изменить uba в элементе 1 списка на: ‘прежде не использовался’».

Параллельные операции

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

Session 1:           update t1 set n1 = 101 where id = 1;
Session 2:           update t1 set n1 = 102 where id = 2;
                     commit;
My session:   set transaction read only;
Session 3:           update t1 set n1 = 99 where id = 3;
                     commit;
My session:   select id, n1 from t1;

Первый вызов в сеансе, который я обозначил как «My session» (мой сеанс) устанавливает уровень изоляции «только для чтения» (см. главу 2), фактически фиксируя состояние базы данных (с точки зрения этого сеанса), в каком она находилась в этот момент времени или, точнее говоря, для данного значения SCN.

С этого момента к сеансу «My session» будут применяться два ограничения: во-первых, ему не позволено будет видеть любые неподтвержденные изменения, производимые другими пользователями (что, впрочем, характерно также для стандартного в Oracle уровня изоляции read committed – чтение подтвержденных изменений) и, во-вторых, в этом сеансе вообще нельзя будет увидеть никакие подтвержденные изменения, выполненные после этого момента.

То есть, когда я выполняю инструкцию select, неподтвержденные изменения, выполненные в сеансе 1, и подтвержденные, выполненные в сеансе 3, должны оставаться невидимыми для меня и я должен видеть только подтвержденные изменения, выполненные в сеансе 2. Мой запрос должен вернуть множество результатов: (1,1), (2,102), (3,3). С другой стороны, из-за того, что Oracle изменяет блоки почти в реальном масштабе времени, к моменту запуска моего запроса все изменения будут выполнены в копиях блоков, находящихся в памяти. Что же происходит внутри Oracle, что позволяет мне получить правильные результаты?

Ниже приводится сокращенный до минимума дамп блока после того, как три сеанса выполнят свои изменения, непосредственно перед запуском моего запроса: 

Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x000a.00e.00001b93 0x01800518.04f5.34 --U- 1 fsc 0x0000.01731c83
0x02 0x0004.00c.00001964 0x018036ad.05ff.3a ---- 1 fsc 0x0000.00000000
0x12:pri[0] offs=0x1f7b
0x14:pri[1] offs=0x1f71
0x16:pri[2] offs=0x1f85
tab 0, row 0, @0x1f7b
tl: 10 fb: --H-FL-- lb: 0x2 cc: 2
col 0: [ 2] c1 02
col 1: [ 3] c2 02 02
tab 0, row 1, @0x1f71
tl: 10 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [ 2] c1 03
col 1: [ 3] c2 02 03
tab 0, row 2, @0x1f85
tl: 9 fb: --H-FL-- lb: 0x1 cc: 2
col 0: [ 2] c1 04
col 1: [ 2] c1 64

Я вынудил Oracle записать этот блок на диск вызовом alter system checkpoint перед выводом дампа, чтобы показать, что все изменения (включая неподтвержденные, выполненные в сеансе 1) находятся не только в буферизованных копиях затронутых блоков, но также могут быть сохранены на диск. Обратите внимание, что строки 0 и 1 изменили свое местоположение в блоке (0x1f7b и 0x1f71 вместо прежних смещений 0x1f97 и 0x1f8d, которые можно видеть в предыдущем дампе) – так как длины строк увеличились, их пришлось скопировать в свободное пространство в блоке, чтобы сохранить изменения. Строка 2, напротив, осталась на прежнем месте (0x1f85), потому что ее длина не изменилась и ее оказалось возможным изменить на месте.

Взгляните внимательнее на список ITL: несмотря на то, что было выполнено три транзакции с этим блоком, в списке по-прежнему имеется всего два элемента. Это объясняется тем, что Oracle стремится сохранить список ITL максимально коротким и повторно использует элементы списка ITL, описывающие уже подтвержденные транзакции. (Элементы ITL повторно используются в порядке увеличения номеров SCN.)

Элемент списка ITL с индексом 0x01 демонстрирует эффект фиксирующей очистки (commit cleanout), быстрой, но неполной очистки, которая может применяться к некоторым измененным блокам при подтверждении транзакции. Флаг получает значение --U-, но счетчик блокировок остается равным 1, а поле Scn/Fsc остается равным fsc, даже при том, что само значение подтвержденного SCN равно 0x01731c83. Если заглянуть в тело блока, можно увидеть, что строка 2 (третья) имеет значений 0x01 в байте блокировки (lb:) – строка 2 является единственной, заблокированной транзакцией 1 в списке ITL. Этот элемент списка соответствует третьей транзакции, которая присвоила столбцу n1 новое значение 99 и была подтверждена.

Элемент 0x02 списка ITL выглядит так, как если бы соответствующая ему транзакция еще не была подтверждена. По причинам, с которыми мы познакомимся ниже, такая ситуация может интерпретироваться ошибочно – вполне возможно, что транзакция была подтверждена некоторое время тому назад, но Oracle еще не успел выполнить очистку и пометить транзакцию как подтвержденную. Фактически, этот элемент ITL хранит информацию о первой, неподтвержденной транзакции и мы можем видеть, что она блокирует одну строку (Lck = 1 в элементе ITL), и что строка 0 – это строка в блоке, которая была заблокирована элементом 0x2 ITL (lb: = 0x2 в теле блока).

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

Рис. 1. Связи между элементом ITL и соответствующим undo-сегментом

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

Oracle и непроцедурный доступ ...
Oracle и непроцедурный доступ ... 8510 просмотров Antoni Tue, 21 Nov 2017, 13:32:50
Видеокурс по администрированию...
Видеокурс по администрированию... 10719 просмотров Илья Дергунов Mon, 14 May 2018, 05:08:47
Блоки данных Oracle: размер бл...
Блоки данных Oracle: размер бл... 7697 просмотров Natali Fri, 27 Jul 2018, 06:01:01
Поддерживаемые Oracle типы дан...
Поддерживаемые Oracle типы дан... 9518 просмотров Валерий Павлюков Wed, 24 Oct 2018, 08:00:37
Войдите чтобы комментировать

apv аватар
apv ответил в теме #8899 6 года 2 мес. назад
ошибка ORA-08177 верный спутник криво написанных приложений под базы данных Oracle.