Эта страница переведена машинным переводом. Читайте английский оригинал. English

Библиотека IBSurgeon

Структура физической базы данных (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 открыл базу данных:

![](/images/IB DB structure/InterBase database structure_html_m4f802417.png)

Рисунок 1. Список страниц базы данных

Страницы могут быть разных типов, каждый из которых служит определенной цели. Взаимосвязи различных типов условно представлены на рисунке 2. Рисунок 2 схематически изображает распределение страниц в файле базы данных - слева направо, сверху вниз, если считать от начала файла. Страницы одного типа не идут строго одна за другой - они могут быть легко перемешаны, размещены в файле в том порядке, в котором они были созданы сервером при расширении или создании базы данных.

![](/images/IB DB structure/InterBase database structure_html_4005a6b0.png)

Рисунок 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 Генераторы идентификаторов
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 offset последнего обновления - устарело */

} *PAG;

Типы страниц и их использование

Рассмотрим каждый тип страницы подробно и узнаем об их функциях и содержащейся в них информации. Мы начнем шаг за шагом - с первой страницы.

Любая операция с базой данных начинается с чтения страницы заголовка базы данных (или страницы заголовка). Страница заголовка базы данных идет первой во всех файлах баз данных. Соответственно, она представлена первой на рисунке 2 (если представить, что рисунок представляет собой расширение файла базы данных слева направо, сверху вниз).

Страница заголовка содержит информацию о базе данных в целом. На рисунке 3 страница данных представлена так, как ее показывает нам IBSurgeon:

![](/images/IB DB structure/InterBase database structure_html_m3c792928.png)

Рисунок 3. Страница заголовка базы данных.

Вы можете получить представление о содержимом страницы заголовка, получив статистику базы данных. Для этого можно использовать утилиту командной строки gstat или другой более удобный инструмент для администрирования InterBase из списка в приложении «Инструменты администратора и разработчика InterBase». Более подробно о процессе получения статистики и описании данных страницы заголовка см. главу «Статистика».

Следует отметить, что страница заголовка содержит такую важную информацию, как размер страницы, номер версии ODS (информацию о ней вы найдете ниже), данные о создании базы данных, информацию о транзакциях и набор различной другой информации. Например, Implementation ID хранит информацию о том, под какой операционной системой была создана эта база данных.

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

На странице заголовка есть ссылка на первую из страниц указателей, хранящих ссылки на страницы данных, содержащие метаданные: таблицу RDB$Pages (см. ниже в главе «Логическая структура базы данных InterBase»). На рисунке 2 эта ссылка проиллюстрирована стрелкой с надписью «Номер 1-й страницы указателей в базе данных». Сервер считывает номер 1-й страницы указателей со страницы заголовка и переходит к ней. Страница указателей состоит из упорядоченного массива номеров страниц данных, составляющих определенную таблицу (таблица рассматривается как SQL-объект, описанный логической структурой базы данных). Теперь вы можете увидеть, как IBSurgeon интерпретирует страницу указателей (см. рисунок 4):

![](/images/IB DB structure/InterBase database structure_html_3bb85660.png)

Рисунок 4. Страница указателей базы данных InterBase

Страница содержит вектор страниц данных; эти данные составляют определенную таблицу в базе данных. Этот вектор представляет собой массив указателей, соответствующих номерам страниц данных в файле. Сервер считывает 4-байтовый номер страницы данных и переходит к необходимой странице данных. Когда он переходит к 1-й странице данных RDB$Pages, сервер начинает построение внутреннего представления базы данных, которое в дальнейшем используется сервером для всех операций с базой данных. RDB$Pages хранит ссылки не только на страницы данных, содержащие информацию о базе данных, но и на остальные страницы, которые играют роль в обеспечении работы базы данных.

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

Одним из важных типов страниц является страница инвентаризации транзакций (TIP). Эти страницы, как и все страницы, состоят из заголовка и основной части, представляющей собой массив 2-байтовых последовательностей. Последовательности описывают состояние транзакций в базе данных (более подробно о транзакциях см. главу «Транзакции»).

Таблица 4. Возможные состояния транзакций в TIP

Значение последовательности на PIP Смысл
0 Транзакция не началась, активна или потеряна без коммита или отката
1 Транзакция выполнила Commit
2 Транзакция выполнила Rollback
3 Limbo-транзакция (для 2PC)

Каждая версия записи имеет свой идентификатор транзакции, что позволяет одновременно выполняемым транзакциям «узнавать» о состоянии друг друга и разрешать конфликты во время многопользовательской работы (см. главу «Многопоколенная архитектура InterBase» для получения дополнительной информации о версиях записей и других деталях).

Страница заголовка базы данных, страницы указателей и TIP относятся к «служебным» типам страниц, используемым только сервером. Пользователи InterBase никогда явно не получают информацию, которую они содержат. Страницы, хранящие информацию о распределении страниц (обычно упоминаемые как страницы инвентаризации страниц (PIP) или страницы инвентаризации пространства (SIP)), также относятся к служебному типу страниц. Эти страницы расположены начиная со второй, то есть первая PIP идет сразу после страницы заголовка, и появляются в базе данных через фиксированные интервалы страниц других типов. Размер этих интервалов означает, через какое количество страниц других типов появляется PIP, и зависит от размера страницы, установленного для этой базы данных. Страницы инвентаризации страниц не учитываются на страницах указателей и не указываются в RDB$Pages. Целостность этих страниц жизненно важна для успешной работы всей базы, поскольку содержимое PIP описывает состояние всех остальных страниц в базе данных. Каждая страница базы данных может иметь 3 состояния: не выделена, выделена с пространством, выделена и полна. Когда возникает необходимость в дополнительном пространстве для новых данных, сервер проверяет PIP на предмет наличия не выделенных страниц. Если такая страница есть, сервер изменяет ее состояние на выделенную с пространством. Если не выделенных страниц нет, база данных расширяется - добавляется новая страница данных.

Пример страницы данных в IBSurgeon и данные, которые она содержит, приведены на рисунке 5.

![](/images/IB DB structure/InterBase database structure_html_1cb26c4f.png)

Рисунок 5. Страница инвентаризации страниц

Как только страница выделена, InterBase записывает ее состояние на SIP, а затем записывает саму страницу. После этого нам нужно добавить эту вновь сформированную страницу к некоторому большому количеству страниц, например, к страницам данных для таблицы. Для этого мы должны записать ссылку на эту новую страницу на последней странице этого большого количества страниц - например, на последней странице данных таблицы. Если сервер прервал свою работу сразу после записи на SIP, но не записал ссылку на страницы, которые ссылаются на только что выделенную страницу, то эта страница становится осиротевшей. Осиротевшая страница физически создана, зарезервирована на SIP, но на нее нет ссылок с других страниц, что означает, что сервер не сможет ее найти и записать данные на диск. Осиротевшая страница отмечена красным квадратом на рисунке 2. Осиротевшие страницы в основном возникают в результате неожиданного отключения питания сервера и «лечатся» специальным инструментом для ремонта базы данных gfix (или FirstAID) (или IBSurFirstAID).

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

На рисунке 6 вы можете увидеть генераторную страницу. Обратите внимание, что хотя IBSurgeon показывает имена генераторов, это не означает, что эти имена хранятся на генераторных страницах. Это сделано для удобства пользователя, изучающего базу данных. В действительности имена генераторов хранятся в системной таблице RDB$Generators.

![](/images/IB DB structure/InterBase database structure_html_7728f31.png)

Рисунок 6. Страница генераторов (g en-ids)

Как вы видите в этом примере, база данных содержит системные генераторы, начинающиеся с префикса RDB$, и пользовательские генераторы. Если вы хотите узнать о функциях и использовании генераторов при разработке приложений баз данных InterBase, обратитесь к главе «Таблицы. Первичные ключи и генераторы». Страницы генераторов учитываются наряду с другими страницами в таблице RDB$Pages.

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

![](/images/IB DB structure/InterBase database structure_html_1c46b1cd.png)

Рисунок 7. Корневая страница индекса

Корневая страница индекса содержит список страниц, на которых хранятся значения индексов, а также информацию об индексе - селективность индекса и различные флаги. Более подробно об индексах, их роли и использовании в базах данных InterBase см. главу «Индексы».

Страницы индексов содержат непосредственно значения индексов или, если уровень индекса > 0, ссылки на нижележащие страницы индексов. Вот пример страницы индекса (рисунок 8).

![](/images/IB DB structure/InterBase database structure_html_m35b3f5ff.png)

Рисунок 8. Страница индекса (B-дерева)

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

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

Пример представления страницы данных в IBSurgeon приведен на рисунке 9:

![](/images/IB DB structure/InterBase database structure_html_235e0c51.png)

Рисунок 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 структуры находится в файле из набора исходных кодов InterBase ods.h. 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++, С++ Builder, Delphi и т.д.).

Заключение

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

Тем не менее, мы считаем, что каждому программисту было бы полезно ознакомиться с содержимым продукта, которым он пользуется каждый день.

Библиография

  1. «The On-Disk Structure of InterBase» by Ann.W.Harrison

  2. «Space Management in InterBase» by Ann W.Harrison

  3. «Structure of a Data Page» by Paul Beach (With thanks to Dave Schnepper and Deej Bredenberg)