Немного информации о фрагментации в блоке данных (data block fragmentation)

dbstalker, 03 ноября

Есть такое явление в ORACLE - data block fragmentation (DBF). Прошу не путать с chained and migrated rows - фрагментацией строк (row fragmentation, RF) и фрагментацией табличного пространства ( tablespace freespace fragmentation, TFF).

Как оракл размещает записи в блоке? Стоит ли администратору уделять внимание и время борьбе с блочной фрагментацией?

В чем состоит суть вопроса по фрагментации в блоке? Дело в том, что, что после удаления любой записи остальные записи в блоке не сдвигаются, чтобы заполнить освободившееся пространство и собрать свободное пространство в одном месте. Блок получается фрагментированным. Реорганизация (дефрагментация) блока ораклом выполняется только тогда, когда при insert/update свободного места в блоке хватает, но нет возможности записать строку в непрерывный кусок .

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

Немного углубимся в данный вопрос.

Начнем со структуры блока. Есть немного информации об этом в моем посте «Формат блока в ORACLE». Как дамп блока получить смотрите в этом посте .

Блок состоит из заголовка блока (block header) и данных блока (data layer).

Заголовок блока (Block header) – это информация о типе блока, ITL, информация об адресе блока на диске.

Start dump data blocks tsn: 0 file#: 1 minblk 84618 maxblk 84618
buffer tsn: 0 rdba: 0x00414a8a (1/84618)
scn: 0x000b.a27a809f seq: 0x02 flg: 0x06 tail: 0x809f0602
frmt: 0x02 chkval: 0x24c9 type: 0x06=trans data
…
Block header dump:  0x00414a8a
 Object id on Block? Y
 seg/obj: 0x57634  csc: 0x0b.a48cdbb3  itc: 2  flg: O  typ: 1 - DATA
     fsl: 0  fnx: 0x0 ver: 0x01
 
 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc
0x01   0x000e.012.000239f6  0x00818058.40a5.15  --U-    1  fsc 0x0000.a48cdbbf
0x02   0x0004.01d.00013c03  0x00b21e3c.3346.46  C---    0  scn 0x000b.a27a809f

Данные (data layer) – это заголовок данных (data header), каталог таблиц, каталог записей, свободное пространство, пространство непосредственно данных и хвост (4 контрольных байта в конце блок).

каталог таблиц содержит информацию о таблицах, чьи строки хранятся в блоке

каталог строк (row directory) - массив указателей (смещение) расположения строк.

data_block_dump,data header at 0x1326085c
===============
tsiz: 0x1fa0
hsiz: 0x14
pbl: 0x1326085c
bdba: 0x00414a8a
     76543210
flag=--------
ntab=1
nrow=1
frre=-1
fsbo=0x14
fseo=0x1f9a
avsp=0x1f83
tosp=0x1f83
0xe:pti[0] nrow=1 количество записей в таблице offs=0 -  каталог таблиц
0x12:pri[0] смещения в блоке до начала записи offs=0x1f9a  - каталог строк

Первый байт после data header - это первая запись в table directory, обозначается как pti[0] в дампе блока. Эта сущность показывает количество записей в таблице, содержащейся в блоке, и смещение (offset) начала записей для этой таблицы в row directory.

Запись в row directory содержит смещение заголовка записи таблицы. Смещения могут указывать на любое место в области данных блока (data layer), которая начинается после row directory.

Каждая строка имеет физическую позицию в блоке (смещение) и логическую позицию в каталоге строк (row directory)

Посмотрим теперь на взаимосвязь ROWID записи и каталога записей в блоке. Выполним для тестовой таблицы с несколькими записями следующее:

select rowid r, dbms_rowid.rowid_relative_fno(rowid) file_number, 
         dbms_rowid.rowid_block_number(rowid) block_number, 
        dbms_rowid.rowid_row_number(rowid) row_number from table_test

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

alter system dump datafile номер_файла данных block номер_блока

Область данных состоит из строчек. Строка имеет структуру:

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

Каждая строка имеет 3 –х байтовый заголовок:

первый байт ( fb) – флаг:

D = Deleted row
H means header in this rows. 
K = Cluster Key
C = Cluster table member
P = First column continues from previous piece
N = Last column continues in next piece
F means First piece of the row. 
L means last piece of the row. 
Since both FL are present in here there is no row-chaining. 

второй байт (lb) - Lock байт - может быть hex 00 (нет блокировки) или 01 до ff (номер слота в ITL, где содержится информация о транзакции, которая держит блокировку на строку.

Третий байт (cc ) – количество полей в записи (Column Count) показывает количество столбцов (хранимых в данном блоке) в строке. Это число может отличаться от реального количества столбцов в таблице, например, если строка сцеплена и ее остаток в другом блоке или если последние столбцы таблицы содержат NULL (в этом случае они просто не хранятся).

Счетчик свободного пространства хранится в заголовке данных (data header), чтобы можно было легко определить место для вставки новой записи в этот блок:

  • fsbo - смещение начала непрерывной области свободного места в блоке
  • fseo - конец этой области
  • avsp - общий размер свободной области в блоке, включая фрагментированное
  • tosp размер свободной области в блоке, доступное после commit

размер всех компонентов блока можно посмотреть вот так:

select "COMPONENT","TYPE","DESCRIPTION","TYPE_SIZE" from v$type_size

Теперь разберемся с тем, как записи вставляются (insert) в блок.

Вставка записей начинается с низа (хвоста) блока и продвигается вверх, потребляя свободное пространство по мере вставки. Новая строка добавляется "выше" существующих, даже если между строками или между строками и хвостом блока есть достаточное для вставки место. То есть при Insert Оракл использует первый свободный слот в row directory и захватывает пространство в свободной области.

Удаление (delete) записи.

При удалении строк они физически остаются на своих местах, но к флагу в их row header добавляется бит D - признак удаленной строки. В table directory и в row directory не происходит никаких изменений – остаются те же смещения для удаленных строк и тоже количество строк.

Обратите внимание на важный момент: если транзакция будет завершена rollback-ом, то не произойдет очистки бита D, а будет вставлена удаленная строка обратно в блок, как обычно, на новое место, "выше" всех существующих записей.

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

Теперь обновление (update) записи.

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

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

Вывод: при update (при котором длина записи изменяется) и при insert запись помещается в область свободного пространства сверху записей уже существующих в блоке.

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

Удаленные записи не исчезают, они дефрагментируются с длиной 2 байта ( flag+lock byte). После дефрагментации пространство, освободившееся после операций обновления и удаления, присоединится к непрерывному свободному пространству (fseo-fsbo). После чего будет доступно для операций вставки и обновлений.

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

В контексте затронутой темы желательно ознакомиться с постом «Формат блока в ORACLE» и ”Мигрирующие и сцепленные строки”

Все что здесь написано легко проверить, выполняя дампы блоков. Если найдете в тексте ошибки или неточности, просим сообщить. Наша благодарность не будет иметь границ! :)

Источник

 

Новый комментарий

Я не спамер: введите суму 6+0



 

От авторов блога

О Блоге - прочитай перед началом.

Задать вопрос и получить ответ - уже решено 94 вопросов

Глоссарий - список терминов и сокращений


 
 

Бизнес форум

Последние темы:

Телепрограмма
23 июня, 1 ответа
Турция
23 июня, 4 ответа
Выбор люстры
22 июня, 1 ответа