Управління простором усередині блоку даних в СУБД Oracle, Інші СУБД, Бази даних, статті

Метою статті є опис процесів, які відбуваються при модифікації вмісту блоку даних. Матеріал носить в основному теоретичний характер і може бути цікавий тим, хто прагне краще зрозуміти внутрішні алгоритми СУБД 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 – заголовок рядка.


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


Отже, давайте зробимо вставку декількох рядків:





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) рядки.


Оригінальний блок (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] – Далі безпосередньо знаходиться сама рядок






Перший блок (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, яку ми видаляли, хоч і займає, як і раніше третього слот row directory, але розташовується по зсуві 0x804 – тобто вона вставлена ​​”вище” всіх, після дефрагментації блоку (в результаті якої рядок a = 4 тепер знаходиться там, де раніше розташовувалася віддалена рядок – по зсуві 0x13c5)

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*