(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Управление пространством внутри блока данных в СУБД Oracle

Источник: oracle
Скулкин Дмитрий, Oracle DBA

Целью статьи является описание процессов, которые происходят при модификации содержимого блока данных. Материал носит в основном теоретический характер и может быть интересен тем, кто стремится лучше понять внутренние алгоритмы СУБД Oracle. Весь материал получен опытным путем и не претендует на полноту и абсолютную достоверность. Эксперименты ставились на обычной heap-таблице без индексов, версия Oracle - 9.2.0.6. Весь материал получен опытным путем и не претендует на полноту и абсолютную достоверность, потому я буду благодарен всем заинтересованным читателям, кто захочет высказать свое мнение по поводу содержащейся в статье информации или дополнить этот материал.

Итак, попытаемся понять, как хранятся строки и что происходит внутри блока данных, когда мы вставляем/обновляем/удаляем строки. Для того, чтобы читателям было легче ориентироваться в терминах и более наглядно представлять себе картину, я привел несколько дампов в качестве иллюстраций.

Для начала давайте разберемся, как хранится строка в блоке. Схематично это можно изобразить так (учитывая, что таблица не в кластере):

{ Flag byte / Lock byte / Column Count byte } { Column length / Column Data / Column length / Column Data /...}

Три байта - Flag + Lock + Column Count вместе составляют row header - заголовок строки.

  • Различные биты Flag байта описывают состояние/расположение строки,
  • Lock байт соответственно показывает, модифицируется ли строка и если да, то с каким ITL-слотом ведется работа,
  • Column Count показывает количество столбцов в строке. Это число может отличаться от реального количества столбцов в таблице, например, если строка сцеплена и ее остаток в другом блоке или если последние столбцы таблицы содержат NULL (в этом случае они просто не хранятся)

Непосредственно данным каждого столбца предшествует его размерность, т.е. сколько байт используется для хранения значения столбца таблицы. Column length принимает значения:

  • FF - если столбец содержит NULL
  • FE+(еще 2 байта длины) - если ширина столбца больше 253 байт, то используется такая конструкция
  • иначе показывает актуальное число байт, используемых для хранения значения столбца

Итак, давайте произведем вставку нескольких строк:

SQL> create table test1 (a number,b varchar(1000)); 
Table created. 
SQL> insert into test1 values (1,'first'); 
1 row created. 
SQL> insert into test1 values (2,'second'); 
1 row created. 
SQL> insert into test1 values (3,'third'); 
1 row created. 
SQL>  select file_id,block_id from dba_extents where segment_name='TEST1'; 
FILE_ID BLOCK_ID 
---------- ---------- 
1 20136 
SQL> alter system dump datafile 1 block 20137; 
System altered. 

Ниже приведен дамп блока данных с моими комментариями, в который вставились наши 3 строчки:

Start dump data blocks tsn: 0 file#: 1 minblk 20137 maxblk 20137 
buffer tsn: 0 rdba: 0x00404ea9 (1/20137) 
scn: 0x0000.002006eb seq: 0x01 flg: 0x00 tail: 0x06eb0601 
frmt: 0x02 chkval: 0x0000 type: 0x06=trans data 
Block header dump: 0x00404ea9 
Object id on Block? Y 
seg/obj: 0x193c csc: 0x00.2006df itc: 2 flg: O typ: 1 - DATA 
fsl: 0 fnx: 0x0 ver: 0x01 
Itl Xid Uba Flag Lck Scn/Fsc 
0x01 0x0004.00f.00000378 0x0080003b.0078.07 ---- 3 fsc 0x0000.00000000 
-- Flag "----" показывает, что транзакция активна 
0x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000 
data_block_dump,data header at 0x10340845c 
=============== 
tsiz: 0x1fa0 
hsiz: 0x18 
pbl: 0x10340845c 
bdba: 0x00404ea9 
76543210 
flag=-------- 
ntab=1 
nrow=3 
frre=-1 
fsbo=0x18 -- смещение начала непрерывной области свободного места в блоке 
fseo=0x1f7b -- конец этой области 

avsp=0x1f63 -- общий размер свободной области в блоке, включая фрагментированное 

tosp=0x1f63 -- размер свободной области в блоке, доступное после commit

0xe:pti[0] nrow=3 offs=0 0x12:pri[0] offs=0x1f94 -- смещение первой строки 0x14:pri[1] offs=0x1f87 -- второй 0x16:pri[2] offs=0x1f7b -- третьей block_row_dump: tab 0, row 0, @0x1f94 tl: 12 fb: --H-FL-- lb: 0x1 cc: 2 -- Lock byte показывает, что транзакция работает с 1-ым ITL-слотом col 0: [ 2] c1 02 -- 1-ый столбец - [длина] значение col 1: [ 5] 66 69 72 73 74 -- 2-ой столбец - [длина] значение tab 0, row 1, @0x1f87 tl: 13 fb: --H-FL-- lb: 0x1 cc: 2 col 0: [ 2] c1 03 col 1: [ 6] 73 65 63 6f 6e 64 tab 0, row 2, @0x1f7b tl: 12 fb: --H-FL-- lb: 0x1 cc: 2 col 0: [ 2] c1 04 col 1: [ 5] 74 68 69 72 64 end_of_block_dump End dump data blocks tsn: 0 file#: 1 minblk 20137 maxblk 20137

Видно, что строки вставляются "снизу вверх", при условии, что низ - это хвост блока, а верх - заголовок блока. Новая строка в большинстве случаев добавляется "выше" существующих, даже если между строками или между строками и хвостом блока есть достаточное для вставки место. Я полагаю, что основание этого - существующий механизм адресации внутри блока, согласно которому нельзя построить матрицу занятого/свободного места в блоке без его полного сканирования (т.к. длина строк хранится только в row header`ах). Даже если строка обновляется с уменьшением размера, то она (фактически это уже новая строка) вставится самой "верхней", а не останется на прежнем месте. Таким образом, при изменениях (update/delete) блок становится фрагментированным - между строк появляются куски свободного места. Единственный случай, когда обновляемая строка остается на своем месте - когда размерности всех полей новой строки идентичны старой (в этом плане использование типа char имеет преимущество над varchar тем, что всегда обеспечивает одинаковую размерность полей, а следовательно и отсутствие фрагментации блока при update`ах).

Можно отметить, что при удалении строк они физически остаются на своих местах, но к их row header`ам добавляется бит D - признак удаленности строки. В row directory не происходит никаких изменений - смещения удаленных строк остаются выставленными в актуальные значения, число строк тоже не изменяется. Последующий rollback (если имеет место быть), тем не менее, не очищает просто бит D, а вставляет удаленную строку обратно в блок как обычно - уже на новое место, "выше" всех. Если же транзакция фиксируется (commit), то дальнейшие действия Oracle зависят от того, возможно ли очистить блок быстро (fast cleanout) или нет.

Если строки удаляются или обновляются с уменьшением размера:

  1. Транзакции, освободившей место, присваивается free space credit (fsc), хранящийся в ITL-слоте транзакции. При удалении Fsc считается как (кол-во_строк)*(длина_строки-2) байт. Два байта, которые вычитаются из длины строки - Flag и Lock байты, они оставляются как идентификатор удаленности и заблокированности строки. Fsc используется транзакцией-владельцем в том случае, когда для вновь вносимых данных в блок без fsc не хватает места (при этом блок дефрагментируется, удаленные строки, если есть, дефрагментируются с длиной 2 байта). Fsc других транзакций может использоваться при условии, что они подтверждены, блок в этом случае еще и полностью очищается (для подтверждения факта фиксации транзакции).
  2. Для того, чтобы транзакции могли использовать освобожденное место, блок дефрагментируется.

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

SQL> delete from test1 where a in (1,2); 
2 rows deleted. 

Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0004.00f.00000378 0x0080003b.0078.09 ---- 3 fsc 0x0015.00000000 
--fsc для 2х удаленных строк (12+13)-2*2=21=0х15
0x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000

data_block_dump,data header at 0x10340845c
===============
fsbo=0x18 
fseo=0x1f7b 
avsp=0x1f63 
tosp=0x1f7c 
0xe:pti[0] nrow=3 offs=0 -- в row directory число строк осталось прежним 
0x12:pri[0] offs=0x1f94 -- и смещения у удаленных строк остались те же самые 
0x14:pri[1] offs=0x1f87 
0x16:pri[2] offs=0x1f7b 
block_row_dump: 
tab 0, row 0, @0x1f94 
tl: 2 fb: --HDFL-- lb: 0x1 
-- к байту флага добавлен бит D - признак удаленности строки, длина равна 2м байтам 
tab 0, row 1, @0x1f87 
tl: 2 fb: --HDFL-- lb: 0x1 
tab 0, row 2, @0x1f7b 
tl: 12 fb: --H-FL-- lb: 0x1 cc: 2 
col 0: [ 2] c1 04 
col 1: [ 5] 74 68 69 72 64 

В результате удаления появилось свободное место в размере 21 байт. Когда же оно будет доступно для новых вставок/обновлений? Оно по-настоящему освободится, т.е. прибавится к непрерывному пулу свободного места (fseo-fsbo) после дефрагментации блока. Дефрагментация происходит когда для вставки новой строки недостаточно места в непрерывной области свободного места (fseo-fsbo), но общего свободного места в блоке для вставки достаточно. При дефрагментации все строки в порядке их следования в row directory заново располагаются со дна блока к заголовку и фрагментация свободного пространства таким образом устраняется.

Ок, дефрагментация позволяет вновь использовать освободившееся пространство в блоке, но не корректирует (т.к. при быстрой очистке удаления строк в нем не отображаются) row directory. Row directory корректируется при полной очистке блока. Рассмотрим типы очисток более подробно.

Очистка блока бывает:

  1. быстрая (fast cleanout). При commit`е Oracle в первую очередь пытается сделать быструю очистку блока. Это означает выставление бита U (commit upper bound) и commit scn в ITL-слоте транзакции. Этого достаточно, чтобы определить факт и время подтверждения транзакции. ITL-слот, прошедший быструю очистку, все равно должен быть в последующем полностью очищен для его использования другой транзакцией, но для этого уже не потребуется чтения заголовка сегмента отката.
  2. частичная (partial cleanout) - очищается только один необходимый ITL-слот. Так, например, делает rollback.
  3. полная (total cleanout). Очищаются все неактуальные lock байты в заголовках строк и в ITL-слотах, освобождвются fsc`ы, устанавливается cleanout scn блока и при необходимости устанавливаются commit scn`ы. Кроме того, корректируется row directory - из него удаляются записи об удаленных строках, и соответстветствующие его слоты могут быть переиспользованы (но не место в блоке, на которое ссылается этот слот - как мы уже говорили, место становится доступно только после дефрагментации). Блок подвергается полной очистке либо при проверке факта заблокированности строки, либо когда транзакция пытается использовать ITL-слот, подвергшийся быстрой очистке, либо когда транзакция пытается использовать fsc другой транзакции. Полная очистка генерирует дополнительное редо для той транзакции, которая делает очистку.

Полную и частичную очистки называют еще отложенными (deferred)

Фрагмент дампа блока с откорректированным после полной очистки row directory:

0xe:pti[0] nrow=16 offs=0
0x12:pri[0] offs=0x1f95 
-- skip --
0x20:pri[7] offs=0x1f49
0x22:pri[8] sfll=9 -- эти слоты row directory теперь могут быть 
0x24:pri[9] sfll=-1 -- перезаписаны
0x26:pri[10] offs=0x1765 

Давайте также рассмотрим вкратце мигрированные/сцепленные (migrated/chained) строки.

  • мигрированная строка - строка, перемещенная из одного блока в другой при ее модификации из-за нехватки места в оригинальном блоке. При этом в оригинальном блоке остается указатель на тот блок, куда переместилась строка, в котором, в свою очередь, есть указатель на оригинальный блок. Если строка вновь модифицируется и вновь требуется ее миграция, то, если в оригинальном блоке есть достаточное место, то строка возвращается в оригинальный блок (т.е. мигрированность ликвидируется), иначе строка мигрирует в новый блок-приемник, а указатель (nrid) в оригинальном блоке с учетом этого корректируется (т.е. Oracle не создает цепочек, когда мигрировавшая строка расположена больше чем в двух блоках). Существование мигрированности или сцепленности определяется Oracle`ом по битам байта flag в row header. При мигрировании/сцеплении создаются дополнительные ITL-слоты и после завершения операции их количество будет max(количество_мигрирующих_строк + ITL_default, существующее_количество_ITL-слотов_в_блоке), ITL_default=2 для heap-таблиц

Оригинальный блок (rdba: 0x00407222):

tab 0, row 1, @0xbf9
tl: 9 fb: --H----- lb: 0x2 cc: 0 -- во флаге row header установлен только бит H (head) nrid: 0x00407223.0 -- указатель на блок/слот, в котором хранится строка Блок-приемник (rdba: 0x00407223): tab 0, row 0, @0xbe8 tl: 5024 fb: ----FL-- lb: 0x1 cc: 5 -- одновременно установленные биты F (first) и L (last) показывают, что строка не сцеплена hrid: 0x00407222.1 -- указатель на блок/слот, из которого мигрировала строка col 0: [1000] -- далее непосредственно находится сама строка
  • сцепленная строка - строка, располагающаяся в нескольких блоках, потому что ее размер больше размера одного блока. Ниже приведены части дампа блоков таблицы, содержащей одну строку из пяти столбцов по 4000 байт каждый. Т.к. размер блока 8 кБ, эта строка занимает 3 блока. Можно отметить, что существует несколько необычный вид мигрированных строк - строки, количество столбцов которых более 255, хранятся сцепленными, но в одном блоке (возникает это потому, что значение "Column Count" в row header имеет размерность 1 байт). В этом случае nrid первой части строки указывает на тот же блок.

Первый блок (rdba: 0x0040722c) 
tab 0, row 0, @0xfcc
tl: 4052 fb: --H-F--N lb: 0x1 cc: 2 
-- F - первая часть фрагмента строки, N (next) - последний столбец продолжается в др. блоке
nrid: 0x0040722b.0 -- указатель на блок, где находится след. часть строки
col 0: [4000] 
col 1: [39] -- из 4000 байт второго столбца в этом блоке находится только 39 
Второй Блок (rdba: 0x0040722b) 
tab 0, row 0, @0x4d
tl: 7995 fb: ------PN lb: 0x1 cc: 3 
-- P (previous) - 1-ый столбец продолжается - его начало в другом блоке
nrid: 0x0040722a.0
col 0: [3961] 
col 1: [4000] 
col 2: [18] 
Третий блок (rdba: 0x0040722a) 
tab 0, row 0, @0x51
tl: 7991 fb: -----LP- lb: 0x1 cc: 2 -- бит L показывает, что это конец строки
col 0: [3982] 
col 1: [4000]

Дополнительная литература:

  1. "Oracle Block Structure", Veljko Lavrnic
  2. "A Close Look at Oracle8i Data Block Internals", Dan Hotka
  3. Учебный курс "DSI402 Space and Transaction Management"

Обсуждение статьи и вопросы:

Анатолий Бачин:
Прошу уточнить следующее Ваше положение:
"Можно отметить, что при удалении строк они физически остаются на своих местах...
Таким образом, при изменениях (update/delete) блок становится фрагментированным - между строк появляются куски свободного места...
Последующий rollback (если имеет место быть), тем не менее, не очищает просто бит D, а вставляет удаленную строку обратно в блок как обычно - уже на новое место, "выше" всех. " (конец цитаты)
Получается, что в случае нескольких последовательных откатов (rollback) блок может переполниться и появятся вытесненные в другие блоки строки.

Дмитрий Скулкин:
В случае rollback не может произойти вытеснение строк в другие блоки, т.к. транзакции, вследствие прошедшей операции удаления,присвоен определенный free space credit, которым она при отсутствии другого свободного места воспользуется. Таким образом все строки останутся в оригинальном блоке.

Давайте за подробностями обратимся к дампам

SQL> create table test1 (a number,b varchar(3000));  

Table created. 

SQL> alter table test1 pctfree 0;   

Table altered. 

SQL> insert into test1 values (1,'qqqqqq');   

1 row created.   

SQL> insert into test1 values (2,'wwwwww');   

1 row created.   

SQL> insert into test1 values (3,rpad('e',3000,'e'));   

1 row created.   

SQL> insert into test1 values (4,rpad('r',3000,'r'));   

1 row created.   

SQL> commit;   

Commit complete.   

SQL> select file_id,block_id,blocks from dba_extents 
   where segment_name='TEST1';   

   FILE_ID   BLOCK_ID     BLOCKS
---------- ---------- ----------
         1      29225          8   

SQL> alter system dump datafile 1 block 29226;   

System altered.   

Я, как и прежде, буду приводить только значимые фрагменты дампов:

 Itl         Xid                  Uba         Flag  Lck        Scn/Fsc
0x01 0x0002.00a.000003c6  0x008000cc.007f.24  --U-   4  fsc 0x0000.001f71d0
0x02 0x0000.000.00000000  0x00000000.0000.00  ----   0  fsc 0x0000.00000000 

fsbo=0x1a
fseo=0x804
avsp=0x7ea
tosp=0x7ea
0xe:pti[0] nrow=4 offs=0
0x12:pri[0] offs=0x1f93
0x14:pri[1] offs=0x1f86
0x16:pri[2] offs=0x13c5
0x18:pri[3] offs=0x804
block_row_dump:
tab 0, row 0, @0x1f93
tl: 13 fb: --H-FL-- lb: 0x1  cc: 2
col  0: [ 2]  c1 02
col  1: [ 6]  71 71 71 71 71 71
tab 0, row 1, @0x1f86
tl: 13 fb: --H-FL-- lb: 0x1  cc: 2
col  0: [ 2]  c1 03
col  1: [ 6]  77 77 77 77 77 77
tab 0, row 2, @0x13c5
tl: 3009 fb: --H-FL-- lb: 0x1  cc: 2
col  0: [ 2]  c1 04
col  1: [3000] -- для читаемости показаны не все данные больших столбцов  

 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
tab 0, row 3, @0x804
tl: 3009 fb: --H-FL-- lb: 0x1  cc: 2
col  0: [ 2]  c1 05
col  1: [3000]
 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72
end_of_block_dump
End dump data blocks tsn: 0 file#: 1 minblk 29226 maxblk 29226 

Мы видим, что свободного места в блоке осталось (0x804-0x1a)=0x7ea=2026 байт. Теперь произведем удаление одной из больших строк и посмотрим, что произойдет.

SQL> delete from test1 where a=3;   

1 row deleted.   

SQL> alter system dump datafile 1 block 29226;   

System altered.  

 

  

  Itl         Xid                  Uba        Flag  Lck        Scn/Fsc
0x01 0x0002.00a.000003c6  0x008000cc.007f.24  C---   0  scn 0x0000.001f71d0
0x02 0x0001.00d.000003c6  0x00800010.007e.01  ----   1  fsc 0x0bbf.00000000 

fsbo=0x1a
fseo=0x804
avsp=0x7ea
tosp=0x13ab
0xe:pti[0] nrow=4 offs=0
0x12:pri[0] offs=0x1f93
0x14:pri[1] offs=0x1f86
0x16:pri[2] offs=0x13c5
0x18:pri[3] offs=0x804
block_row_dump:
tab 0, row 0, @0x1f93
tl: 13 fb: --H-FL-- lb: 0x0  cc: 2
col  0: [ 2]  c1 02
col  1: [ 6]  71 71 71 71 71 71
tab 0, row 1, @0x1f86
tl: 13 fb: --H-FL-- lb: 0x0  cc: 2
col  0: [ 2]  c1 03
col  1: [ 6]  77 77 77 77 77 77
tab 0, row 2, @0x13c5
tl: 2 fb: --HDFL-- lb: 0x2
tab 0, row 3, @0x804
tl: 3009 fb: --H-FL-- lb: 0x0  cc: 2
col  0: [ 2]  c1 05
col  1: [3000]
 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72
end_of_block_dump
End dump data blocks tsn: 0 file#: 1 minblk 29226 maxblk 29226 

Удаленная строка пометилась флагом D в заголовке строки (row header). Теперь, выполняя откат транзакции, Oracle будет вставлять удаленную ранее строчку "выше" остальных. Но, т.к. свободного места выше всех строк недостаточно для вставки 3009-байтной строки, транзакция израсходует свой free space credit (0xbbf=3007 байт), произведет дефрагментацию блока и вставит строку:

SQL> rollback;   

Rollback complete.   

SQL> alter system dump datafile 1 block 29226;   

System altered.  

  

 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x0002.00a.000003c6  0x008000cc.007f.24  C---    0  scn 0x0000.001f71d0
0x02   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000
fsbo=0x1a
fseo=0x804
avsp=0x7ea
tosp=0x7ea
0xe:pti[0] nrow=4 offs=0
0x12:pri[0] offs=0x1f93
0x14:pri[1] offs=0x1f86
0x16:pri[2] offs=0x804
0x18:pri[3] offs=0x13c5
block_row_dump:
tab 0, row 0, @0x1f93
tl: 13 fb: --H-FL-- lb: 0x0  cc: 2
col  0: [ 2]  c1 02
col  1: [ 6]  71 71 71 71 71 71
tab 0, row 1, @0x1f86
tl: 13 fb: --H-FL-- lb: 0x0  cc: 2
col  0: [ 2]  c1 03
col  1: [ 6]  77 77 77 77 77 77
tab 0, row 2, @0x804
tl: 3009 fb: --H-FL-- lb: 0x0  cc: 2
col  0: [ 2]  c1 04
col  1: [3000]
 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
tab 0, row 3, @0x13c5
tl: 3009 fb: --H-FL-- lb: 0x0  cc: 2
col  0: [ 2]  c1 05
col  1: [3000]
 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72
end_of_block_dump
End dump data blocks tsn: 0 file#: 1 minblk 29226 maxblk 29226 

Обратите внимание, что строка, удовлетворяющая условию a=3, которую мы удаляли, хоть и занимает по-прежнему 3-ий слот row directory, но располагается по смещению 0x804 - т.е. она вставлена "выше" всех, после дефрагментации блока (в результате которой строка a=4 теперь находится там, где ранее располагалась удаленная строка - по смещению 0x13c5)

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 07.10.2009 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Oracle Database Personal Edition Named User Plus Software Update License & Support
Oracle Database Standard Edition 2 Processor License
Oracle Database Standard Edition 2 Named User Plus License
Oracle Database Personal Edition Named User Plus License
ReSharper - Commercial annual subscription
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
СУБД Oracle "с нуля"
Краткие описания программ и ссылки на них
ЕRP-Форум. Творческие дискуссии о системах автоматизации
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100