Фізична структура бази даних (InterBase та Firebird)
Олексій Ковязін, Сергій Востріков, останнє оновлення 05-червня-2004
Фізична структура бази даних
Чому ми повинні вивчати фізичну структуру бази даних InterBase?
Зазвичай, коли ми говоримо про фізичну структуру бази даних InterBase, ми маємо на увазі, що вона представляє дані з точки зору низькорівневої організації даних - аж до рівня байтів. Багато програмістів, які розробляють застосунки з використанням мов високого рівня, нехтують вивченням низькорівневих деталей. Однак знання основних принципів організації даних усередині бази даних є ключем до ефективного проєктування застосунків баз даних. Тому ми зробимо екскурс у внутрішню організацію бази даних InterBase і з’ясуємо, як вона влаштована.
Отже, для чого призначена система управління базами даних (СУБД)? Очевидно, для зберігання та керування даними. Це звучить банально, але варто замислитися. Користувач передає дані в СУБД, яка певним чином перетворює ці дані у зрозумілі їй внутрішні формати. Можна уявити «0 та 1», якщо слова «внутрішній формат даних» викликають певні труднощі з асоціаціями. СУБД зберігає ці дані, і в момент першого запиту має витягти їх зі свого формату, перетворити у відповідний вигляд і передати користувачеві.
Предметом цього розділу є те, як СУБД зберігає свої дані, у якому вигляді та як вони організовані на найнижчому рівні. Ми спробуємо пояснити вам, як із бітів і байтів, що лежать на жорсткому диску, ми отримуємо цінні дані.
Файли бази даних InterBase
Зазвичай, коли ми говоримо про базу даних, ми маємо на увазі саму СУБД, користувацьку інформацію та навіть програми клієнтів, які працюють із даними. У цьому розділі ми розглядатимемо базу даних як файли бази даних.
База даних InterBase являє собою один або кілька файлів, що містять інформацію про все, що пов’язано з цією базою. Винятком є інформація про користувачів, оскільки користувачі визначаються на рівні всього сервера і зберігаються окремо, у базі даних безпеки admin.ib (у версіях до 7 це було ISC4.GDB).
Порада: Зверніться до розділу «Безпека сервера та бази даних», щоб детальніше вивчити принципи безпеки InterBase.
Отже, вся інформація про базу даних зберігається в цих файлах: самі дані, індекси, тригери, збережені процедури тощо.
База даних InterBase для середнього проєкту являє собою один файл, оскільки сучасні версії InterBase можуть використовувати 64-бітний ввід/вивід для роботи з файлом даних, що дає змогу мати файл даних розміром до 64 ГБ. Раніші версії InterBase мали обмеження 4 гігабайти на кожен файл бази даних (до 64 ТБ для всієї бази даних). Як ми можемо припустити, 64 гігабайти цілком достатньо для зберігання інформації майже будь-якого застосунку бази даних. Але якщо це необхідно, ми можемо розділити базу даних на кілька файлів. До речі, існують бази даних InterBase розміром у сотні гігабайт.
IBSurgeon - путівник по базі даних InterBase
Ми повинні детально знати структуру файлів бази даних InterBase. І тому бажано мати будь-який зручний інструмент, який дозволяє працювати безпосередньо з файлами бази даних, а не за допомогою ядра сервера InterBase. Найпростіший спосіб - використати звичайний шістнадцятковий переглядач і спробувати зрозуміти структуру файлів бази даних, розглядаючи їх HEX-представлення. Це була б досить стомлююча робота.
Але, на щастя, існує інструмент для прямої роботи з базами даних InterBase. Це IBSurgeon Editor - інструмент для прямої низькорівневої роботи з базами даних InterBase, який можна використовувати для вивчення внутрішньої структури баз даних InterBase та діагностики пошкоджених баз даних з метою їх відновлення. Детальніше див. у додатку «Інструменти адміністратора та розробника InterBase».
IBSurgeon використовує власний альтернативний механізм доступу до бази даних, який дозволяє відкривати та переглядати бази даних у будь-якому стані, включно з сильно пошкодженими, які не може відкрити ядро сервера InterBase/FireBird/Yaffil.
Ми використаємо IBSurgeon для ілюстрації внутрішньої структури бази даних.
Файли *.IB/*.FDB зсередини
IB - це розширення, рекомендоване для файлів баз даних InterBase, а FDB - для Firebird (раніше це було GDB). Перше, що ми маємо сказати про структуру файлу IB, це те, що він являє собою набір сторінок строго визначеного розміру. Розмір файлу бази даних кратний розміру сторінки, який є незмінним для всіх файлів цієї бази даних. Різні версії InterBase підтримують різні розміри сторінок, що показано в таблиці 1. Розмір сторінки встановлюється під час створення бази даних і не може бути змінений протягом її життєвого циклу. Іншими словами, ми можемо змінити розмір сторінки лише під час відновлення бази даних із резервної копії.
Таблиця 1. Розміри сторінок, що підтримуються різними версіями InterBase
| Версія InterBase | Розмір сторінки, байти | ||||
| 1024 | 2048 | 4096 | 8192 | 16384 | |
| InterBase 4.0 | * | * | * | * | |
| InterBase 5.x | * | * | * | * | |
| InterBase 6.x-7.x | * | * | * | * |
Читання та запис даних у базі даних виконуються посторінково, багато важливих характеристик сервера та бази даних, таких як розмір кешу бази даних, залежать від розміру сторінки та обчислюються в «сторінках».
Давайте відкриємо будь-яку базу даних InterBase за допомогою IBSurgeon. Достатньо двічі клацнути по файлу бази даних. Малюнок 1 показує список сторінок, який з’являється після відкриття бази даних IBSurgeon:

Малюнок 1. Список сторінок бази даних
Сторінки можуть бути різних типів, кожен із яких служить певній меті. Взаємозалежності різних типів умовно зображені на малюнку 2. Малюнок 2 схематично показує розподіл сторінок у файлі бази даних - зліва направо, зверху вниз, якщо рахувати від початку файлу. Сторінки одного типу не йдуть строго одна за одною - вони можуть бути перемішані, розподілені у файлі в порядку їх створення сервером під час розширення або створення баз даних.

Малюнок 2. Взаємозалежності між різними типами сторінок у базі даних InterBase
Ви, напевно, помітили, що деякі типи сторінок не мають посилань на інші типи сторінок. Однак тут немає суперечності, річ у тім, що ці типи сторінок пов’язані та використовуються на іншому структурному рівні. Вони можуть бути пов’язані з таблицею RDB$PAGES та іншими системними таблицями (цю таблицю та інші системні об’єкти ми розглянемо нижче - у розділі «Логічна структура бази даних»). На малюнку 2 ми можемо бачити лише явні посилання між сторінками на фізичному рівні.
Розглянемо детально, які типи сторінок існують у базі даних InterBase. У файлі ods.h із набору первинних кодів InterBase міститься інформація про всі можливі типи сторінок. Ми часто звертатимемося до цього файлу, щоб отримати дані не лише про ODS, а й про багато інших фундаментальних речей ядра InterBase в оригінальному вихідному коді. Усього оголошено 11 типів сторінок, але лише 9 із них варті пояснення (це добре видно з таблиці 2). Типи сторінок з ідентифікаторами 0 та 1 є невизначеними або невикористовуваними.
Таблиця 3. Типи сторінок у FB
| Визначення в ods.h | Ідентифікатор типу сторінки | Опис сторінки |
| pag_undefined | 0 | Невизначена - якщо сторінка має цей тип, вона, ймовірно, вільна |
| pag_header | 1 | Заголовна сторінка бази даних |
| pag_pages | 2 | Сторінка інвентаризації сторінок (або сторінка інвентаризації простору - SIP) |
| pag_transactions | 3 | Сторінка інвентаризації транзакцій (TIP) |
| pag_pointer | 4 | Сторінка покажчиків |
| pag_data | 5 | Сторінка даних |
| pag_root | 6 | Коренева сторінка індексу |
| pag_index | 7 | Сторінка індексу (B-дерева) |
| pag_blob | 8 | Сторінка даних Blob |
| pag_ids | 9 | Gen-ids |
| pag_log | 10 | Інформація журналу попереднього запису |
Кожна сторінка має свій заголовок, який містить інформацію про тип сторінки та номер наступної сторінки того самого типу. Повний список параметрів, які містить заголовок кожної сторінки, ми можемо отримати, якщо розглянемо структуру pag у файлі визначень ods.h.
/\* Базовий заголовок сторінки \*/
typedef struct pag {
SCHAR pag\_type; /\* ідентифікатор типу сторінки \*/
SCHAR pag\_flags; /\* прапорці сторінки \*/
USHORT pag\_checksum; /\* контрольна сума сторінки: вона дорівнює 12345 після версії 5.0 \*/
ULONG pag\_generation; /\* покоління сторінки \*/
ULONG pag\_seqno; /\* WAL seqno останнього оновлення - застаріло\*/
ULONG pag\_offset; /\* WAL зміщення останнього оновлення - застаріло\*/
} \*PAG;
#### Типи сторінок та їх використання
Розглянемо кожен тип сторінки детально та дізнаємось про їх функції та інформацію, яку вони містять. Почнемо крок за кроком - з першої сторінки.
Будь-яка операція з базою даних починається з читання сторінки заголовка бази даних (або головної сторінки). Сторінка заголовка бази даних йде першою у всіх файлах баз даних. Відповідно, вона зображена першою на малюнку 2 (якщо уявити, що малюнок представляє розширення файлу бази даних зліва направо, зверху вниз).
Сторінка заголовка містить інформацію про базу даних в цілому. На малюнку 3 сторінка даних показана так, як її представляє нам IBSurgeon:

Малюнок 3. Сторінка заголовка бази даних.
Ви можете отримати уявлення про вміст сторінки заголовка, отримавши статистику бази даних. Для цього можна скористатися утилітою командного рядка **gstat** або іншим зручнішим інструментом для адміністрування InterBase зі списку в додатку «Інструменти адміністратора та розробника InterBase». Детальніше про процес отримання статистики та опис сторінки заголовка див. у розділі «Статистика».
Слід зазначити, що сторінка заголовка містить таку важливу інформацію, як розмір сторінки, номер версії ODS (інформацію про це ви знайдете нижче), дані про створення бази даних, інформацію про транзакції та набір різної інформації. Наприклад, Implementation ID зберігає інформацію про те, під якою операційною системою була створена ця база даних.
Під час підключення до бази даних сервер InterBase зчитує перші 1024 байти інформації з початку файлу та визначає за зчитаними значеннями, чи є файл, вказаний у рядку підключення, базою даних InterBase. Потім сервер зчитує номер версії ODS зі сторінки заголовка та розмір сторінки в цій базі даних, і, якщо версія ODS сумісна з реалізацією сервера, він повторно зчитує всю сторінку заголовка, використовуючи відповідний розмір сторінки, отриманий з перших 1024 байтів. Після цього зі сторінки заголовка зчитуються решта важливих параметрів бази даних, такі як режим читання-запису, діалект бази даних тощо.
На сторінці заголовка є посилання на першу зі сторінок покажчиків, які зберігають посилання на сторінки даних, що містять метадані: таблиця RDB$Pages (див. нижче у розділі «Логічна структура бази даних InterBase»). На малюнку 2 це посилання проілюстровано стрілкою з написом «Номер 1-ї сторінки покажчика в базі даних». Сервер зчитує номер 1-ї сторінки покажчика зі сторінки заголовка та переходить до неї. Сторінка покажчика складається з упорядкованого масиву номерів сторінок даних, які утворюють певну таблицю (таблиця розглядається як SQL-об'єкт, описаний логічною структурою бази даних). Тепер ви можете побачити, як IBSurgeon інтерпретує сторінку покажчика (див. малюнок 4):

Малюнок 4. Сторінка покажчика бази даних InterBase
Сторінка містить вектор сторінок даних; ці дані утворюють певну таблицю в базі даних. Цей вектор являє собою масив покажчиків, що відповідають номерам сторінок даних у файлі. Сервер зчитує 4-байтовий номер сторінки даних і переходить до необхідної сторінки даних. Коли він переходить до 1-ї сторінки даних RDB$Pages, сервер починає побудову внутрішнього представлення бази даних, яке надалі використовується сервером для всіх операцій з базою даних. RDB$Pages зберігає посилання не лише на сторінки даних, що містять інформацію про базу даних, але й на інші сторінки, які відіграють роль у забезпеченні роботи бази даних.
Ми часто згадуємо цю таблицю, яка, строго кажучи, відноситься до логічної структури бази даних. Тим не менш, все взаємопов'язано, тому ми не можемо описати щось, не посилаючись на щось інше.
Одним із важливих типів сторінок є сторінка інвентаризації транзакцій (TIP). Ці сторінки, як і всі сторінки, складаються із заголовка та основної частини, що представляє собою масив 2-байтових послідовностей. Послідовності описують стан транзакцій у базі даних (детальніше про транзакції див. у розділі «Транзакції»).
Таблиця 4. Можливі стани транзакцій у TIP
| | |
| --- | --- |
| Значення послідовності на PIP | Значення |
| 0 | Транзакція не розпочата, активна або втрачена без commit або rollback |
| 1 | Транзакція виконала Commit |
| 2 | Транзакція виконала Rollback |
| 3 | Limbo-транзакція (для 2PC) |
Кожна версія запису має свій ідентифікатор транзакції, що дозволяє одночасно виконуваним транзакціям «дізнаватися» про стан одна одної та вирішувати конфлікти під час багатокористувацької роботи (див. розділ «Багатопоколінна архітектура InterBase», щоб дізнатися більше про версії записів та інше).
Сторінка заголовка бази даних, сторінки покажчиків та TIP відносяться до «службових» типів сторінок, які використовуються лише сервером. Користувачі InterBase ніколи явно не отримують інформацію, яку вони містять. Сторінки, що зберігають інформацію про розподіл сторінок (зазвичай їх називають Page Inventory Pages (PIP) або Space Inventory Pages (SIP)), також відносяться до службового типу сторінок. Ці сторінки розташовані, починаючи з другої, тобто перша PIP йде одразу після сторінки заголовка, і з'являються в базі даних через фіксовані інтервали сторінок інших типів. Розмір цих інтервалів означає, через скільки сторінок інших типів з'являється PIP, і залежить від розміру сторінки, встановленого для цієї бази даних. Page Inventory Pages не враховуються на сторінках покажчиків і не вказуються в RDB$Pages. Цілісність цих сторінок є життєво важливою для успішної роботи всієї бази, оскільки вміст PIP описує стан усіх інших сторінок у базі даних. Кожна сторінка бази даних може мати 3 стани: не розподілена, розподілена з простором, розподілена та _повна._ Коли виникає потреба в додатковому просторі для нових даних, сервер перевіряє PIP на наявність _нерозподілених_ сторінок. Якщо така сторінка існує, сервер змінює її стан на розподілена з простором. Якщо _нерозподілених_ сторінок немає, база даних розширюється - додається нова сторінка даних.
Приклад сторінки даних в IBSurgeon та дані, які вона містить, наведено на малюнку 5.

Малюнок 5. Page Inventory Page
Щойно сторінку розподілено, InterBase записує її стан на SIP, а потім записує саму сторінку. Після цього нам потрібно додати цю щойно сформовану сторінку до якоїсь великої кількості сторінок, наприклад, до сторінок даних для таблиці. Для цього ми повинні записати посилання на цю нову сторінку на останній сторінці цієї великої кількості сторінок - наприклад, на останній сторінці даних таблиці. Якщо сервер перервав свою роботу одразу після запису на SIP, але не записав посилання на сторінки, які посилаються на щойно розподілену сторінку, то ця сторінка стає «осиротілою». Осиротіла сторінка фізично створена, зарезервована на SIP, але на неї немає посилань з інших сторінок, це означає, що сервер не зможе її знайти та записати дані на диск. Осиротіла сторінка позначена червоним квадратом на малюнку 2. Осиротілі сторінки здебільшого виникають внаслідок несподіваного відключення живлення сервера і «лікуються» спеціальним інструментом для ремонту бази даних **gfix** (або FirstAID) (або IBSurFirstAID).
Перш ніж розглядати сторінки даних, слід згадати про важливі типи сторінок: _сторінки генераторів_ та _сторінки індексів._ Сторінки генераторів являють собою масив 4-байтових чисел, що показують стани генераторів. Власне, генератор - це звичайний лічильник.
На малюнку 6 ви можете побачити сторінку генератора. Зверніть увагу, що хоча IBSurgeon показує назви генераторів, це не означає, що ці назви зберігаються на сторінках генераторів. Це зроблено для зручності користувача, який вивчає базу даних. Насправді назви генераторів зберігаються в системній таблиці RDB$Generators.

Рисунок 6. Сторінка генераторів (g en-ids )
Як ви бачите в цьому прикладі, база даних містить системні генератори, що починаються з префікса RDB$, та генератори, визначені користувачем. Якщо ви хочете дізнатися про функції та використання генераторів під час розробки застосунків баз даних InterBase, дивіться розділ «Таблиці. Первинні ключі та генератори». Сторінки генераторів враховуються разом з іншими сторінками в таблиці RDB$Pages.
Кожна таблиця має принаймні одну кореневу сторінку індексів, незалежно від того, чи має вона індекси. Ця сторінка містить покажчики на сторінки індексів для відповідної таблиці. Можна сказати, що коренева сторінка індексів має таке ж значення для сторінок індексів, як сторінка-покажчик для сторінок даних. Тому IBSurgeon представляє її подібним чином. Приклад кореневої сторінки індексів наведено на рисунку 7.

Рисунок 7. Коренева сторінка індексів
Коренева сторінка індексів містить список сторінок, де зберігаються значення індексів, а також інформацію про індекс - селективність індексу та різні прапорці. Детальніше про індекси, їх роль та використання в базах даних InterBase дивіться в розділі «Індекси».
Сторінки індексів містять безпосередньо значення індексів, або, якщо рівень індексу >0, посилання на нижчі сторінки індексів. Ось приклад сторінки індексу (рисунок 8).

Рисунок 8. Сторінка індексу (B-дерева)
Сторінка індексу зберігає упаковані значення індексованих даних. Використовується досить складний механізм індексації, особливо при створенні складених індексів (що включають кілька полів).
Загалом сторінки даних і сторінки, що містять BLOB-значення, зберігають інформацію користувача. Сторінки даних містять записи в таблицях користувача бази даних, фрагменти записів, старі версії, відмінності між версіями, BLOB-поля тощо. Що стосується BLOB-полів, вони пов’язані із записами на сторінках даних і містять дані великого розміру, які не можуть бути розміщені на сторінці даних. Посилальний тип зберігання BLOB-значень дозволяє зберігати великі обсяги даних.
Приклад представлення сторінки даних в IBSurgeon наведено на рисунку 9:

Рисунок 9. Сторінка даних
Заголовок сторінки даних містить тип сторінки, ідентифікатор таблиці-власника (relationID). Записи зберігаються на сторінках даних з кінця сторінки, і вони розміщуються ближче до початку сторінки в міру заповнення.
Ми можемо переконатися в цьому, якщо подивимося на індекси рядків, що містять 2 значення - зміщення на сторінці та його довжину. Як ви бачите на початку рядка, є записи, розміщені в кінці сторінки - наприклад, перший запис має зміщення 8156 байтів і довжину 34 байти - отже, він закінчується на 8156+34=8192 байти - на самому краю сторінки (у нашому випадку розмір сторінки становить 8192 байти). Коли сторінка заповнюється (даними зверху та індексами записів знизу), сервер починає записувати нові записи та версії старих записів на нові сторінки. З описаного вище механізму заповнення сторінки легко зрозуміти, чому фахівці InterBase наполегливо рекомендують використовувати сторінки даних великого розміру (мінімум 4096 байтів, краще 8192). Якщо ми створимо таблицю, один запис якої буде досить великого розміру (наприклад, 10 полів VARCHAR (255)), вони займуть, будуть заповнені, понад 2550 байтів. Це означає, що такий запис буде занадто великим для сторінки малого розміру (1024 або 2048). Очевидно, що необхідність завантажувати кілька сторінок з диска для читання одного запису не прискорить роботу з вашою базою даних. Тому рекомендується перевизначити розмір сторінки даних при створенні або відновленні бази даних, оскільки за замовчуванням встановлено розмір 1024 байти. Ми щойно коротко розглянули основні типи сторінок файлів даних InterBase та їх функції. Тепер ми можемо перейти до вищого структурного рівня.
ODS
ODS - це абревіатура від On-Disk Structure, тобто структура даних бази даних InterBase на диску. ODS визначає, як організовані дані у файлах бази даних. Визначення основних констант і структур даних для реалізації On-Disk структури міститься у файлі ods.h із набору вихідних кодів InterBase. ODS змінювалася в процесі розробки InterBase, і під час роботи з конкретною базою даних сервер визначає номер версії ODS, щоб знати, з чим він має справу. Файл ods.h представляє нам такі версії On-Disk структури:
-
ODS 5 використовувалася InterBase 3.3 і не підтримується вищими версіями
-
ODS 6 та ODS 7 ніколи не виходили
-
ODS 8 використовується InterBase 4.0
-
ODS 9 використовується InterBase 4.5 та вище
-
ODS 10 вийшла з InterBase 6
-
ODS 11 вийшла з InterBase 7.0
Окрім основних версій ODS, існують мінорні версії, які залежать від конкретної версії сервера бази даних, що їх створив. Основні номери версії записуються в цілій частині числа, що вказує версію, мінорні - у дробовій частині. Наприклад, сервер версії 4.0 створює бази даних з ODS 8.0, а InterBase 4.2 - 8.2. Перехід між мінорними версіями знизу вгору виконується автоматично. Наприклад, достатньо відкрити базу з ODS 8.0, створену сервером 4.0, за допомогою InterBase 5.6, і ODS цієї бази даних матиме версію 8.2. Перехід між основними версіями бази даних виконується лише через резервне копіювання бази даних за допомогою старої версії та відновлення за допомогою нової версії сервера. Процес переходу між версіями детально описано в розділі 1.4 «Міграція».
Важливим моментом у реалізації підтримки ODS для версій InterBase 4.x та 5.x є зворотна сумісність серверів InterBase 4.x та 5.x з версією на одиницю меншою, ніж реалізація конкретного сервера. InterBase підтримує кілька можливих ODS, і відповідно до своєї версії ODS при підключенні до конкретної бази даних обирає підтримку необхідної реалізації ODS. Механізм прийняття рішення про те, яку реалізацію підтримки ODS обрати в конкретному випадку, називається Y-Valve ((c) Стіва Трентона).
Простіше кажучи, базу даних з ODS 8.x, що відповідає InterBase 4.0, можна відкрити в InterBase 5.x.
Повну таблицю сумісності ODS наведено нижче:
| Версія InterBase | Основна ODS | Мінорна ODS |
| 4.0/4.1 | 8.0 | … |
| 4.2 | 8.2 | 8.2 |
| 5.0/5.1 | 9.0 | 8.2 |
| 5.5 | 9.1 | 8.2 |
| 5.6 | 9.1 | 8.2 |
| 6.0 | 10.0 | 9.0/9.1 |
| 7.0 | 11.0 | 10.0 |
| 7.1 | 11.1 | 10.0 |
ODS має зворотну сумісність. Іншими словами, сервер з вищою версією та всі його інструменти зможуть працювати з базою даних, створеною серверами ранніх версій, але не навпаки. Якщо ви спробуєте відкрити базу даних, створену у версії InterBase 6, за допомогою InterBase 5.x, ви отримаєте повідомлення про помилку «Unsupported On-disk structure: Found ODS 10, supported ODS 9».
Опис переходу між версіями знизу вгору та навпаки дивіться в розділі «Міграція».
ODS дуже важлива для питань, що стосуються резервного копіювання та вилучення бази даних, а також відновлення пошкоджених баз даних. Інструменти резервного копіювання gbak та відновлення gfix стежать за версією ODS і просто не працюватимуть, якщо версія ODS бази даних, якій вони мають служити, більша за версію, реалізовану в них. Це означає, що gbak з 4.x не зможе створити резервну копію бази даних, якщо вона створена сервером 5.x, однак легко навпаки.
Міст між фізичною та логічною структурою бази даних
Ми розглянули фізичну структуру файлів бази даних загалом. Тепер нам потрібно перейти до логічної структури бази даних. Давайте зробимо міст між фізичним та логічним рівнями представлення інформації в базі даних, щоб не було розмежування в поняттях і прогалин у матеріалі. Все, що зберігається на різних сторінках бази даних, має бути певним чином організоване в пам’яті комп’ютера; дані з файлу бази даних мають бути перетворені в набір внутрішньосерверних об’єктів і змінних. Цей набір називається внутрішнім образом бази даних відповідно до термінології Енн Гаррісон [1.. Отже, ми спробуємо розглянути процес створення внутрішнього образу бази даних.
-
Сервер читає 1024 байти з початку файлу, і якщо це дійсно файл бази даних InterBase, він визначає розмір сторінки цієї бази та повторно зчитує всю заголовну сторінку.
-
Із заголовка сервер сторінок витягує номер сторінки покажчиків, яка зберігає посилання на сторінки даних, визначаючи таблицю RDB$Pages.
-
Сервер переходить до цієї сторінки покажчиків і починає зчитувати інформацію зі сторінок даних, на які вона вказує. Він заповнює першу таблицю RDB$Pages даними. Ця таблиця є чимось на кшталт мосту між фізичними об’єктами - сторінками файлів бази даних - та логічними - таблицями. Структура RDB$Pages, як і інших системних таблиць, строго фіксована в InterBase.
-
Отримавши дані про розподіл сторінок за відношеннями (відношення - насправді це те саме, що й звичайні таблиці, і ми можемо подумки замінювати ці поняття для спрощення), InterBase починає формувати структури даних: спочатку системні таблиці, обмеження та індекси, а потім і користувацькі об’єкти.
-
Після ініціалізації системних і користувацьких метаданих (таблиць, обмежень, індексів та інших об’єктів бази даних) InterBase повертає дескриптор цієї бази даних користувачу, який попросив відкрити базу. По суті, дескриптор - це ідентифікатор, який показує InterBase, з якою базою даних працювати, оскільки кілька користувачів можуть працювати одночасно, а це означає, що може бути відкрито кілька баз даних.
-
Після цих операцій база даних вважається відкритою, і сервер готовий виконувати користувацькі запити до неї. Тепер, коли створено певний міст, що з’єднує фізичну та логічну структуру бази даних, ми можемо почати вивчати особливості логічної структури.
Логічна структура бази даних InterBase
Логічна структура - досить розмите поняття, тому ми спробуємо поступово засвоїти ключові ідеї, сподіваючись, що згодом вони стануть інтуїтивно зрозумілими. Перше, що ми розглянемо стосовно логічної структури бази даних, - це системні таблиці та їхній вміст. Системні таблиці описують систему, а також користувацькі метадані. Загалом кажучи, термін «метадані» означає «дані, що описують набір даних». Префікс «мета» означає: «описує набір». Наприклад, метамова - це мова, що описує набір мов. Метадані описують користувацькі дані, тобто таблиці, тригери, представлення, збережені процедури тощо - усе, що реалізує правила зберігання та обробки інформації, заради якої створено цю конкретну базу даних.
Досить цікаво вперше дізнатися, що всі метадані - користувацькі таблиці, тригери, представлення, а також усі системні об’єкти - зберігаються в тих самих таблицях, з яких можна читати та записувати дані звичайними SQL-запитами. Ці таблиці «візуально» відрізняються лише тим, що їхні назви починаються з RDB$. Ці 4 символи зарезервовані для назв системних об’єктів. Жодна користувацька таблиця, стовпець чи інший об’єкт не має права мати назви, що починаються з цих символів. Формально ви можете створити таблицю, назва якої починається із зарезервованих символів, але документація InterBase не рекомендує цього робити.
Виникає питання: якщо дані про структуру бази даних зберігаються в тих самих таблицях, що й користувацькі дані, то де зберігається інформація про таблиці, які описують таблиці? Класичний приклад проблеми «курки та яйця» - як одне могло з’явитися раніше за інше, якщо вони взаємозалежні? Відповідь полягає в тому, що системні таблиці в їхньому примітивному стані зафіксовані в початкових кодах InterBase і автоматично відкриваються під час створення бази даних у визначеному порядку. Ми вже говорили про таблицю RDB$Pages, яка зіставляє фізичні сторінки у файлах бази даних із певними об’єктами цієї бази. Структуру цієї таблиці наведено нижче:
Таблиця 5. Системна таблиця RDB$Pages
| Назва стовпця | Тип даних | Опис |
| RDB$PAGE_NUMBER | INTEGER | Номер фізичної сторінки |
| RDB$RELATION_ID | SMALLINT | Ідентифікатор таблиці, для якої виділено сторінку |
| RDB$PAGE_SEQUENCE | INTEGER | Номер цієї сторінки |
| RDB$PAGE_TYPE | SMALLINT | Тип сторінки - див. таблицю 3 |
Кожна сторінка даних пов’язана з певною таблицею. Цей зв’язок підтримується полем RDB$RELATION_ID, де зберігається посилання на таблицю. Як описано вище, у процесі побудови внутрішнього образу бази даних сервер створює цю таблицю та заповнює її даними за заданим алгоритмом. Якщо бути точним, на момент побудови внутрішнього образу бази даних RDB$Pages - це не таблиця, а просто файл даних певного формату, відомого InterBase. За фіксованим алгоритмом сервер зчитує дані з цього файлу та створює таблицю - RDB$Relations - яка є важливою для всієї бази даних. Ця таблиця описує всі таблиці бази даних. Якщо виконати SQL-запит:
SELECT * from RDB$Relations
щоб дізнатися, посилання на які таблиці містить RDB$Relations, ми побачимо, що вона містить RDB$Pages і саму себе. Очевидно, що в цьому випадку сервер трохи хитрує, заднім числом підставляючи ці та інші системні таблиці в RDB$Relations, таким чином легалізуючи їх. Він реєструє їх як «звичайні» таблиці, куди може додавати або видаляти записи. Іншими словами, надає стандартний SQL-інтерфейс для роботи з метаданими.
І може виникнути цілком розумне питання - навіщо розробникам InterBase узгоджувати свої системні дані з користувацьким інтерфейсом? Адже внутрішні механізми доступу та операцій читання були б швидшими. Звісно, у наданні універсального механізму роботи з таблицями, що описують метадані, є великий сенс.
Справа в тому, що логічна структура бази даних складається не лише з таблиць, а й з інших об’єктів. В InterBase існують такі об’єкти:
-
Таблиця
-
Представлення
-
Тригер
-
Обчислюване поле
-
Перевірка (валідація)
-
Процедура
-
Індекс виразу
-
Виняток
-
Користувач
-
Поле
-
Індекс
-
Функція, визначена користувачем (UDF)
Поки що ми точно не знаємо призначення деяких об’єктів, але точно знаємо, що всі вони мають бути описані та збережені в певному вигляді, зручному для користувача та для доступу з ядра InterBase. Найкраще було б зберігати ці об’єкти в системних таблицях. Їхнє додавання та модифікація виконуються SQL-запитами. Розумне рішення, чи не так? Реалізація сервера повністю відокремлена від конкретної бази даних - усі взаємозв’язки описані SQL та його розширеннями - мовою збережених процедур і тригерів.
Отже, усі об’єкти сервера зберігаються в таблицях. Для кожного типу об’єктів існує таблиця, що описує всі екземпляри, описані в базі даних. Наприклад, для тригерів існує таблиця RDB$Triggers, для збережених процедур - RDB$Procedures, представлення описані в таблиці RDB$Relations.
Розглянемо детально структуру останньої таблиці, що описує всі таблиці та представлення в базі даних. Структуру таблиці RDB$RELATIONS взято з Language Reference для InterBase 6 і наведено нижче в таблиці 6.
Таблиця 6. Системна таблиця RDB$Relations
| Назва стовпця | Тип даних | Довжина | Опис |
| RDB$VIEW_BLR | BLOB | 80 | BLR: для представлень містить BLR (Binary Language Representation) запиту, який InterBase виконує щоразу під час звернення до представлення. |
| RDB$VIEW_SOURCE | BLOB | 80 | Текст: для представлень містить код SQL-запиту, що реалізує це представлення. |
| RDB$_DESCRIPTION | BLOB | 80 | Користувацький опис таблиці або представлення |
| RDB$RELATION_ID | SMALLINT | Містить внутрішній ідентифікатор таблиці/представлення | |
| RDB$SYSTEM_FLAG | SMALLINT | Визначає тип таблиці: користувацькі дані - 0; системна інформація > 0. | |
| RDB$DBKEY_LENGTH | SMALLINT | Довжина db$key | |
| RDB$FORMAT | SMALLINT | Зарезервовано для внутрішнього використання InterBase. Містить лічильник модифікацій метаданих для даної таблиці. | |
| RDB$FIELD_ID | SMALLINT | Кількість полів у таблиці. | |
| RDB$RELATION_NAME | CHAR | 31 | Унікальна назва таблиці. |
В описі цієї системної таблиці ми бачимо абревіатуру BLR. Щоб зрозуміти, що це таке, зробимо екскурс у SQL. Як відомо, представлення, тригери та збережені процедури - це код, написаний розширенням мови SQL (для кожного сервера СКБД існують власні розширення). Він близький до людської мови, що дозволяє легко складати запити. Але InterBase, очевидно, перекладає його на щось більш «машинне» - а саме в BLR (Binary Language Representation). Будь-який запит, представлення, тригер, збережена процедура завжди перекладаються в BLR, а потім передаються в ядро InterBase для виконання.
BLR
BLR - це спеціальна мова, яка використовується як проміжна ланка між SQL-кодом, який пише програміст, і машинним кодом, який виконує сервер. Ніхто не пише безпосередньо мовою BLR - це було б досить складно, оскільки для максимально можливої швидкості виконання в цій мові використовується так званий зворотний польський запис. Ось невеликий приклад:
blr_begin,
blr\_assignment,
blr\_field, 0, 7, 'D','A','T','E','I','Z','M',
blr\_variable, 1,0,
blr\_assignment,
blr\_field, 0, 4, 'R','A','T','E',
blr\_variable, 0,0,
blr\_block,
BLR для ваших запитів, процедур, тригерів та інших об’єктів формується спеціальним препроцесором, який є частиною ядра сервера. Як показано в таблиці 7, для представлень зберігається їхній текстовий (початковий) вигляд, а також скомпільоване представлення, тобто BLR. При зверненні до будь-якого об’єкта, що має BLR, сервер виконує бінарний код об’єкта і не інтерпретує початковий текст цих об’єктів щоразу, що дозволяє прискорити виконання складних запитів.
Ієрархія об’єктів в InterBase
Щоб мати чітке уявлення про те, що являють собою об’єкти бази даних, спробуємо побудувати ієрархію об’єктів бази даних за принципом «хто що містить і що». Фізичні сторінки файлів бази даних є першими, що мають бути включені в нашу ієрархію як найнижчий рівень організації даних. Потім йдуть таблиці як основні об’єкти, що описують усі інші типи об’єктів. Таблиці описують збережені процедури, тригери, обчислювані поля, перевірки, індекси за виразами, винятки тощо. Зверніть увагу - лише описують! Таблиці містять лише оголошення та визначення цих об’єктів, а самі об’єкти реалізуються через BLR. Тому ми можемо представити таблиці у вигляді каркаса, що підтримує всі інші об’єкти бази даних. BLR буде внизу каркаса як рівень реалізації, потім тригери, збережені процедури, індекси за виразами та представлення.
Щоб заспокоїти спеціалістів із внутрішньої структури InterBase, які можуть заперечити, що BLR багатьох об’єктів (таких як представлення) зберігається в системних таблицях, зауважимо, що це ставлення досить складно відобразити на малюнку, і для спрощення ми його опустимо. Схема не має на меті абсолютно точно відтворити взаємозалежності об’єктів бази даних; вона лише ілюструє їхній тісний взаємозв’язок.
Той факт, що ці типи об’єктів безпосередньо пов’язані з BLR, який реалізує їх без будь-якої проміжної логіки, об’єднує їх. Винятки слід виділити окремо - вони являють собою особливі типи помилок, визначених користувачем. Винятки обробляються на рівні ядра InterBase і тому не мають BLR. Такі типи обмежень, як перевірки, розташовані вище тригерів, оскільки насправді саме тригери реалізують логіку обмежень і перевірок.
Ієрархію об’єктів логічної та фізичної структури бази даних зображено на малюнку 2.
Малюнок 10. Об’єкти логічної структури бази даних InterBase
Звичайно, ця схема описує логічну структуру та взаємозв’язки об’єктів у базі даних лише приблизно і дає загальне уявлення про неї. Кожен, хто хоче вивчити структуру метаданих бази даних InterBase, може виконати реінжиніринг системних таблиць бази даних і розглянути всі взаємозв’язки між її об’єктами, а також звернутися до документації та вихідних кодів InterBase. Ця таблиця показує лише основні об’єкти бази даних. Коротко опишемо основні функції, які ці об’єкти виконують у базі даних.
Таблиці - основний об’єкт, що містить користувацькі та системні дані. Таблиця має унікальне ім’я та містить набір іменованих полів. Користувач може розміщувати дані, витягувати та змінювати дані в таблицях. Можна сказати, що таблиця подібна до звичайних паперових таблиць, намальованих від руки.
Тригери - виконувані частини коду, які використовуються для реалізації додаткових дій під час операцій з даними. Тригери виконуються до або після операцій вставки, зміни чи видалення і дозволяють реалізувати підстановку значень у новостворені записи та багато інших речей.
Збережена процедура - потужний інструмент для реалізації бізнес-логіки на рівні бази даних. Виконуючись на рівні сервера, вона працює дуже швидко і дозволяє виконувати набір операцій над наборами даних. Збережені процедури InterBase повертають стандартні набори даних SQL, над якими можна виконувати всі SQL-операції, включаючи об’єднання з іншими таблицями.
Представлення - це скомпільовані SQL-запити, які виконуються на сервері. Представлення дозволяють організовувати набори даних, переносячи частину бізнес-логіки на сервер.
Перевірки - це обмеження, що накладаються на значення полів у таблиці. Наприклад, ми можемо вказати, що дане поле прийматиме лише додатні значення. Обмеження на значення полів реалізуються тригерами і дозволяють ефективно контролювати посилальну цілісність на рівні бази даних. Зазвичай обмеження використовуються для запобігання потраплянню в таблицю неправильних значень.
Користувачі - InterBase дозволяє мати кількох користувачів для роботи з базою даних і розподіляти між ними права доступу до різних об’єктів бази даних. Таким чином, ми можемо контролювати дозволи на ті чи інші операції з базою даних.
Функції, визначені користувачем (UDF) - функції, визначені користувачем. Це одна з найпотужніших можливостей InterBase, яка дозволяє розширити стандартний SQL-інтерфейс власними функціями. Наприклад, функції роботи з рядками, такі як UPPER (встановлення всіх символів у верхньому регістрі), реалізовані в стандартній бібліотеці UDF, що входить до комплекту InterBase. Завдяки можливості створювати власні UDF, розробники можуть розширити функціональність InterBase практично будь-якими функціями. Для створення UDF ми можемо використовувати будь-яке середовище програмування, яке дозволяє створювати динамічні бібліотеки (Visual C++, C++ Builder, Delphi тощо).
Висновок
У цьому розділі ми вперше розглянули питання реалізації зберігання та обробки даних у базі даних InterBase. На жаль, ми не можемо зробити короткий огляд цієї теми, не звертаючись до великої кількості термінів і неточних аналогій. Якби ми описали фізичну та логічну структуру бази даних детальніше, нам довелося б звертатися до вихідних кодів InterBase, але це була б інша книга.
Тим не менш, ми вважаємо, що кожному програмісту було б корисно ознайомитися зі змістом продукту, яким він користується щодня.
Бібліографія
-
«The On-Disk Structure of InterBase» by Ann.W.Harrison
-
«Space Management in InterBase» by Ann W.Harrison
-
«Structure of a Data Page» by Paul Beach (With thanks to Dave Schnepper and Deej Bredenberg)