データベースの物理構造(InterBase および Firebird)
Alexey Kovyazin, Sergey Vostrikov、最終更新日:2004年6月5日
データベースの物理構造
なぜInterBaseデータベースの物理構造を学ぶ必要があるのか?
通常、InterBaseデータベースの物理構造について話すとき、それはデータをバイトレベルに至るまでの低レベルのデータ編成の観点から表すことを意味します。高水準言語を使用してアプリケーションを開発する多くのプログラマーは、低レベルの詳細を学ぶことを軽視しています。しかし、データベース内部のデータ編成の主要な原則を知ることは、データベースアプリケーションの効果的な設計への鍵となります。そこで、InterBaseデータベース編成の内部への探訪を行い、それがどのように構成されているかを明らかにします。
では、データベース管理システム(DBMS)は何のためにあるのでしょうか?明らかに、データの保存と制御のためです。これは平凡に聞こえますが、考える価値があります。ユーザーはデータをDBMSに渡し、DBMSは何らかの方法でこれらのデータを自身が理解できる内部形式に変換します。「内部データ形式」という言葉が連想しにくい場合は、「0と1」を想像できます。DBMSはこれらのデータを保存し、最初の要求時にその形式からデータを抽出し、適切なビューに変換してユーザーに提供します。
この章の主題は、DBMSがデータをどのように保存するか、どのようなビューで、最低レベルでどのように編成されているかです。HDD上にあるビットとバイトから、どのようにして価値あるデータを得るのかを説明します。
InterBaseデータベースファイル
通常、データベースについて話すとき、DBMS自体とユーザー情報、さらにはデータを扱うクライアントプログラムを意味します。この章では、データベースをデータベースファイルとして考えます。
InterBaseデータベースは、このベースに関連するすべての情報を含む1つまたは複数のファイルを表します。ユーザー情報は例外です。ユーザーはサーバー全体のレベルで定義され、セキュリティデータベースadmin.ib(7より前のバージョンではISC4.GDBでした)に別途保存されるためです。
アドバイス:「サーバーとデータベースのセキュリティ」の章を参照して、InterBaseのセキュリティ原則について詳しく学んでください。
つまり、データベースに関するすべての情報は、これらのファイル内に保存されます:データ自体、インデックス、トリガー、ストアドプロシージャなどです。
平均的なプロジェクトのInterBaseデータベースは1つのファイルを表します。現代のInterBaseバージョンは64ビットIOを使用してデータファイルを操作できるため、最大64GBのデータファイルを持つことができます。以前のInterBaseバージョンでは、各データベースファイルあたり4ギガバイト(データベース全体で最大64テラバイト)の制約がありました。推測できるように、64ギガバイトはほとんどすべてのデータベースアプリケーションの情報を保存するのに十分です。しかし、必要であれば、データベースを複数のファイルに分割できます。ちなみに、数百ギガバイトのサイズのInterBaseデータベースも存在します。
IBSurgeon - InterBaseデータベースのガイド
InterBaseデータベースファイルの構造を詳細に知る必要があります。そのため、InterBaseサーバーカーネルを介さずにデータベースファイルを直接操作できる便利なツールを持つことが望ましいです。最も簡単な方法は、通常の16進数ビューアを使用して、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 | * | * | * | * |
データベース内のデータの読み書きはページ単位で実行され、データベースキャッシュのサイズなどの多くの重要なサーバーおよびデータベース特性はページサイズに依存し、「ページ」単位で数えられます。
IBSurgeonで任意のInterBaseデータベースを開いてみましょう。データベースファイルをダブルクリックするだけで十分です。図1は、IBSurgeonがデータベースを開いた後に表示されるページのリストを表しています:

図1. データベースページのリスト
ページには異なるタイプがあり、それぞれが特定の目的を果たします。異なるタイプの相互依存関係は、図2に条件付きで表されています。図2は、データベースファイル内のページの割り当てを模式的に示しています - ファイルの先頭から数えて、左から右へ、上から下へです。同じタイプのページは厳密に連続して配置されるわけではありません - サーバーがデータベースを拡張または作成するときに作成された順序で、ファイル内に簡単に混在して割り当てられます。

図2. InterBaseデータベース内の異なるタイプのページ間の相互依存関係
一部のタイプのページが他のタイプのページへの参照を持たないことに気づいたかもしれません。しかし、ここに矛盾はありません。問題は、これらのタイプのページが別の構造レベルでリンクされ、使用されることです。それらはRDB$PAGESテーブルや他のシステムテーブルにリンクできます(このテーブルと他のシステムオブジェクトについては、後の章「データベースの論理構造」で検討します)。図2では、物理レベルでのページ間の明示的な参照のみを見ることができます。
InterBaseデータベースにどのようなタイプのページがあるかを詳しく考えてみましょう。InterBaseの主要コードのセットからのods.hファイルには、可能なすべてのページタイプに関する情報があります。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 | 先行書き込みログ情報 |
すべてのページにはヘッダーがあり、ページタイプと同じタイプの次のページの番号に関する情報が含まれています。各ページヘッダーに含まれるパラメータの完全なリストは、定義ファイルods.hのpag構造を検討すれば取得できます。
/* 基本页面头部 */
typedef struct pag {
SCHAR pag_type; /页面类型标识符/
SCHAR pag_flags; /页面标志/
USHORT pag_checksum; /页面校验和:5.0版本后等于12345/
ULONG pag_generation; /页面代次/
ULONG pag_seqno; /* 上次更新的WAL序列号 - 已弃用*/
ULONG pag_offset; /* 上次更新的WAL偏移量 - 已弃用*/
} *PAG;
页面类型及其用途
让我们详细考虑每种页面类型,了解它们的功能和包含的信息。我们将一步一步来–从第一页开始。
对数据库的任何操作都始于读取数据库头页(或头页)。数据库头页在所有数据库文件中排在第一位。因此,它在图2中首先显示(如果我们想象该图表示数据库文件从左到右、从上到下的扩展)。
头页包含关于整个数据库的信息。在图3中,数据页以IBSurgeon向我们展示的方式呈现:

图3. 数据库头页。
通过获取数据库统计信息,您可以了解头页的内容。为此,您可以使用命令行工具 gstat 或应用程序「管理员和InterBase开发者工具」列表中其他更方便的InterBase管理工具。有关接收统计信息过程和头页数据描述的更多详细信息,请参阅「统计信息」章节。
应当注意,头页包含诸如页面大小、ODS版本号(相关信息见下文)、数据库创建日期、事务信息以及各种其他信息等重要信息。例如,实现ID存储了关于该数据库在何种操作系统下创建的信息。
连接数据库时,InterBase服务器从文件开头读取前1024字节的信息,并根据读取的值判断连接行中指定的文件是否为InterBase数据库。然后服务器从头页读取ODS版本号和该数据库的页面大小,如果ODS版本与服务器实现兼容,则使用从前1024字节获得的适当页面大小重新读取整个头页。之后,从头页读取其余重要的数据库参数,如读写模式、数据库方言等。
头页上有对第一个指针页的引用,指针页存储指向包含元数据的数据页的引用:RDB$Pages表(见下文「InterBase数据库逻辑结构」章节)。在图2中,此引用用带有「数据库中第1个指针页的编号」字样的箭头表示。服务器从头页读取第1个指针页的编号,然后继续到该页。指针页由有序的数据页编号数组组成,这些数据页构成某个表(表被视为SQL对象,由数据库逻辑结构描述)。现在您可以看到IBSurgeon如何解释指针页(见图4):

图4. InterBase数据库指针页
该页包含数据页向量;这些数据构成数据库中的某个表。该向量表示一个指针数组,对应于文件中数据页的编号。服务器读取4字节的数据页编号,然后继续到所需的数据页。当它继续到RDB$Pages的第1个数据页时,服务器开始构建内部数据库表示,该表示随后被服务器用于所有数据库操作。RDB$Pages不仅存储指向包含数据库信息的数据页的引用,还存储指向在提供数据库工作中发挥作用的其他页面的引用。
我们经常提到这个表,严格来说它属于数据库逻辑结构。然而,一切都是相互关联的,因此我们不能在不涉及其他内容的情况下描述某事物。
重要的页面类型之一是事务清单页(TIP)。这些页面像所有页面一样由头部和主体部分组成,主体部分表示2字节序列的数组。这些序列描述数据库中事务的状态(有关事务的更多详细信息,请参阅「事务」章节)。
表4. TIP中可能的事务状态
| PIP上的序列值 | 含义 |
| 0 | 事务未开始、活动或丢失(未提交或回滚) |
| 1 | 事务执行了提交 |
| 2 | 事务执行了回滚 |
| 3 | 悬空事务(用于2PC) |
每个记录版本都有其事务标识符,这使得同时执行的事务能够「了解」彼此的状态,并在多用户工作中解决冲突(有关记录版本和其他内容的更多信息,请参阅「InterBase多代架构」章节)。
数据库头页、指针页和TIP属于「维护」页面类型,仅供服务器使用。InterBase用户永远不会显式获取它们包含的信息。存储页面分配信息的页面(通常称为页面清单页(PIP)或空间清单页(SIP))也属于维护页面类型。这些页面从第二页开始,即第一个PIP紧跟在头页之后,并以固定页面间隔出现在其他类型的页面之间。这些间隔的大小表示PIP每隔多少其他类型的页面出现一次,取决于为该数据库设置的页面大小。页面清单页不计入指针页,也不在RDB$Pages中指向。这些页面的完整性对整个数据库的成功工作至关重要,因为PIP内容描述了数据库中所有其他页面的状态。每个数据库页面可以有3种状态:未分配、已分配且有空间、已分配且_已满_。当需要为新数据提供额外空间时,服务器检查PIP以查找_未分配_的页面。如果存在这样的页面,服务器将其状态修改为已分配且有空间。如果没有_未分配_的页面,数据库将扩展–添加新的数据页。
IBSurgeon中数据页及其包含数据的示例见图5。

图5. 页面清单页
一旦页面被分配,InterBase在SIP上写入其状态,然后写入页面本身。之后,我们必须将这个新形成的页面添加到某个大量页面中,例如表的数据库页面。为此,我们应该在这个大量页面的最后一页上写入对此新页面的引用–例如,在表的最后一个数据页上。如果服务器在写入SIP后中断工作,但未在引用刚分配页面的页面上写入引用,则该页面成为孤儿页。孤儿页在物理上已创建,在SIP上已保留,但没有来自其他页面的引用,这意味着服务器将无法找到它并在磁盘上写入数据。孤儿页在图2中用红色方块标记。孤儿页大多由于服务器意外断电而产生,并通过数据库修复专用工具 gfix(或FirstAID)(或IBSurFirstAID)进行「修复」。
在考虑数据页之前,我们应提及重要的页面类型:生成器_和_索引 页面。生成器页表示4字节数字的数组,显示生成器的状态。实际上,生成器是一个普通的计数器。
在图6中您可以看到生成器页。请注意,尽管IBSurgeon显示生成器名称,但这些名称并不存储在生成器页上。这样做是为了方便研究数据库的用户。实际上,生成器名称存储在系统表RDB$Generators中。

図6. ジェネレータページ(g en-ids)
この例でわかるように、データベースにはRDB$プレフィックスで始まるシステムジェネレータと、ユーザー定義のジェネレータが含まれています。InterBaseデータベースアプリケーションを開発する際のジェネレータの機能と使用法については、「テーブル。主キーとジェネレータ」の章を参照してください。ジェネレータページは、他のページと同様にRDB$Pagesテーブルで計上されます。
すべてのテーブルには、インデックスがあるかどうかに関係なく、少なくとも1つのインデックスルートページがあります。このページには、該当するテーブルのインデックスページへのポインタが含まれています。インデックスルートページは、データページにとってのポインタページと同じ重要性を持つと言えます。そのため、IBSurgeonはこれを同様の方法で表現します。インデックスルートページの例を図7に示します。

図7. インデックスルートページ
インデックスルートページには、インデックス値が格納されているページのリストと、インデックス情報(インデックスの選択性やさまざまなフラグ)が含まれています。インデックスとそのInterBaseデータベースでの役割と使用法の詳細については、「インデックス」の章を参照してください。
インデックスページには、インデックスの値が直接格納されるか、インデックスレベルが0より大きい場合は、下位のインデックスページへの参照が格納されます。インデックスページの例を次に示します(図8)。

図8. インデックス(B-tree)ページ
インデックスページには、インデックス化されたデータのパックされた値が格納されます。特に複合インデックス(複数のフィールドを含む)を作成する場合には、かなり複雑なインデックス化のメカニズムが使用されます。
一般に、データページとBLOB値を含むページにはユーザー情報が格納されます。データページには、データベースのユーザーテーブルのレコード、レコードの断片、古いバージョン、バージョン間の差分、BLOBフィールドなどが含まれます。BLOBフィールドについては、データページ上のレコードに接続されており、データページに配置できない大きなサイズのデータを含みます。BLOB値を参照型で格納することで、大きなデータを格納できます。
IBSurgeonでのデータページ表示の例を図9に示します:

図9. データページ
データページのヘッダーには、ページタイプ、所有者テーブル識別子(relationID)が含まれています。レコードはデータページの末尾から格納され、埋まるにつれてページの先頭に近い方に割り当てられます。
行インデックス(オフセットとその長さの2つの値を含む)を見ると、これを確認できます。ご覧のとおり、行の先頭にはページの末尾に割り当てられたレコードがあります。たとえば、最初のレコードのオフセットは8156バイト、長さは34バイトなので、8156+34=8192バイトで終了します。これはページのちょうど端です(この場合、ページサイズは8192バイトです)。ページが(上部からのデータと下部からのレコードインデックスで)埋まると、サーバーは新しいページに新しいレコードと古いレコードのバージョンを書き込み始めます。上記のページ充填のメカニズムから、InterBaseの専門家が大きなサイズのデータページ(最低4096バイト、できれば8192バイト)の使用を強く推奨する理由が簡単にわかります。1つのレコードがかなり大きなサイズになるテーブル(たとえば、VARCHAR(255)の10フィールド)を作成すると、それらは2550バイト以上を占有し、埋めることになります。つまり、そのようなレコードは小さなサイズのページ(1024または2048バイト)には大きすぎるということです。1つのレコードを読み取るためにディスクから複数のページをロードする必要があると、データベースでの作業が速くなることは明らかではありません。したがって、データベースを作成または復元するときにデータページのサイズを再定義することをお勧めします。デフォルトでは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はODS 8.2のデータベースを作成します。マイナーバージョン間の下位から上位への移行は自動的に実行されます。たとえば、サーバー4.0で作成されたODS 8.0のデータベースをInterBase 5.6で開くだけで、このデータベースのODSはバージョン8.2になります。メジャーデータベースバージョン間の移行は、古いバージョンを使用したデータベースバックアップと、新しいサーバーバージョンを使用したリストアによってのみ実行されます。バージョン間の移行プロセスについては、章1.4「移行」で詳しく説明されています。
InterBaseバージョン4.xおよび5.xのODSサポートの実装における重要な点は、InterBaseサーバー4.xおよび5.xが、特定のサーバーの実装より1つ少ないバージョンとの後方互換性を持つことです。InterBaseは複数の可能なODSをサポートしており、特定のデータベースに接続するときのODSバージョンに応じて、必要なODS実装のサポートを選択します。特定のケースでどのODSサポート実装を選択するかを決定するメカニズムは、Y-Valve(スティーブ・トレントンの著作権)と呼ばれます。
簡単に言えば、InterBase 4.0に対応するODS 8.xのデータベースは、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バージョンがツールに実装されているバージョンより大きい場合は動作しません。つまり、4.xのgbakは、5.xのサーバーで作成されたデータベースのバックアップを作成できませんが、その逆は簡単にできます。
データベースの物理構造と論理構造の橋渡し
データベースファイルの物理構造について概説しました。次に、データベースの論理構造に進む必要があります。データベース内の情報表現の物理レベルと論理レベルの間に橋渡しをして、概念の区切りや資料のギャップがないようにしましょう。さまざまなデータベースページに格納されているものはすべて、コンピュータメモリ内で何らかの方法で編成される必要があります。データベースファイルからのデータは、一連のサーバー内オブジェクトと変数に変換される必要があります。このセットは、アン・ハリソンの用語によると、内部データベースイメージと呼ばれます[1.. それでは、内部データベースイメージを作成するプロセスを検討してみましょう。
-
服务器从文件开头读取1024字节,如果确实是InterBase数据库文件,它会确定该数据库的页大小,并重新读取整个头页。
-
从头部,页面服务器提取指向数据页的指针页编号,从而定义RDB$Pages表。
-
服务器继续访问该指针页,并开始从指向的数据页读取信息。它用数据填充第一个RDB$Pages表。该表类似于物理对象(数据库文件的页面)与逻辑对象(表)之间的桥梁。RDB$Pages的结构与其他系统表一样,在InterBase中是严格固定的。
-
在获得关于关系(relations–实际上,它与普通表相同,为简化起见,我们可以在概念上替换这些术语)的页面分配数据后,InterBase开始形成数据结构:首先是系统表、约束和索引,然后是用户对象。
-
在系统元数据和用户元数据(表、约束、索引及其他数据库对象)初始化完成后,InterBase将数据库句柄返回给请求打开数据库的用户。句柄本质上是一个标识符,告诉InterBase要处理哪个数据库,因为多个用户可以同时工作,这意味着可以同时打开多个数据库。
-
完成这些操作后,数据库被视为已打开,服务器准备执行用户的查询。现在,当连接数据库物理和逻辑结构的桥梁建立后,我们可以开始研究逻辑结构的特性。
InterBase数据库逻辑结构
逻辑结构是一个相当模糊的概念,因此我们将尝试逐步掌握关键思想,希望之后它们会变得直观清晰。我们首先考虑与数据库逻辑结构相关的是系统表及其内容。系统表描述系统以及用户元数据。一般来说,术语“元数据”意为“描述一组数据的数据”。前缀“meta”意为“描述一个集合”。例如,元语言是描述一组语言的语言。元数据描述用户数据,即表、触发器、视图、存储过程等–所有实现信息存储和处理规则的内容,正是这些规则使得这个具体数据库得以创建。
第一次知道所有元数据–用户表、触发器、视图以及所有系统对象–都存储在相同的表中,并且可以通过普通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表的结构取自InterBase 6语言参考,如下表6所示。
表6. 系统表RDB$Relations
| 列名 | 数据类型 | 长度 | 描述 |
| RDB$VIEW_BLR | BLOB | 80 | BLR:对于视图,包含InterBase每次访问视图时执行的查询的BLR(二进制语言表示)。 |
| 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语言扩展编写的代码(每个DBMS服务器都有自己的扩展)。它接近人类语言,这使得在其中编写查询很容易。但InterBase显然将其翻译成更“机器化”的东西–即BLR(二进制语言表示)。任何查询、视图、触发器、存储过程总是被翻译成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は実装レイヤーとしてフレームの最下部にあり、その上にトリガー、ストアドプロシージャ、式インデックス、ビューがあります。
(ビューなどの)多くのオブジェクトのBLRがシステムテーブルに保存されていると異議を唱えるかもしれないInterBase内部構造の専門家を落ち着かせるために、この姿勢は図で表現するのがかなり難しく、簡略化のためにそれは無視することにします。このスキームは、データベースオブジェクトの相互依存関係を完全に正確に再現することを目的としておらず、それらの密接な相互接続を説明するだけです。
これらのオブジェクトタイプが、中間ロジックなしでそれらを実装するBLRに直接接続されているという事実が、それらを統合します。例外は別途割り当てる必要があります - これらはユーザーによって定義された特別なタイプのエラーを表します。例外はInterBaseカーネルのレベルで処理されるため、BLRを持ちません。チェックなどの制約タイプはトリガーの上に割り当てられます。実際にはトリガーが制約とチェックのロジックを実装するためです。
データベースの論理構造と物理構造のオブジェクトの階層は、図2に描かれています。
図10. InterBaseデータベースの論理構造のオブジェクト
もちろん、このスキームはデータベース内のオブジェクトの論理構造と相互接続を近似的に説明しているにすぎず、一般的な概念を提供します。InterBaseデータベースのメタデータ構造を研究したい人は誰でも、データベースシステムテーブルのリエンジニアリングを実行し、そのオブジェクト間のすべての相互接続を検討したり、ドキュメントやInterBaseの一次コードを参照したりできます。この表は主要なデータベースオブジェクトのみを示しています。これらのオブジェクトがデータベースで実行する主な機能を簡単に説明しましょう。
テーブル - ユーザーデータとシステムデータを含む主要なオブジェクト。テーブルは一意の名前を持ち、名前付きフィールドのセットを含みます。ユーザーはテーブルにデータを配置し、抽出し、変更できます。テーブルは手書きの普通の紙のテーブルに似ていると言えます。
トリガー - データ操作時に追加のアクションを実装するために使用される実行可能なコードの一部。トリガーは挿入、変更、削除操作の前または後に実行され、新しく作成されたレコードへの値の代入やその他多くのことを実現できます。
ストアドプロシージャ は、データベースレベルでビジネスロジックを実装するための強力なツールです。サーバーレベルで実行されるため、非常に高速に動作し、データセットに対して一連の操作を実行できます。InterBaseストアドプロシージャは標準のSQLデータセットを返し、他のテーブルとの統合を含むすべてのSQL操作を実行できます。
ビューは、サーバー上で実行されるコンパイル済みのSQLクエリです。ビューはデータセットを編成し、ビジネスロジックの一部をサーバーに移すことを可能にします。
検証 は、テーブル内のフィールドの値に設定される制約です。たとえば、指定されたフィールドが正の値のみを受け入れるように指定できます。フィールド値の制約はトリガーによって実装され、データベースレベルで参照整合性を効果的に制御できます。通常、制約はテーブルに誤った値が配置されるのを防ぐために使用されます。
ユーザー - InterBaseでは、データベースを操作するために複数のユーザーを持つことができ、異なるデータベースオブジェクトへのアクセス権をユーザー間で分配できます。これにより、特定のデータベース操作への許可を制御できます。
ユーザー定義関数(UDF) - ユーザーによって定義された関数。これはInterBaseの最も強力な機能の1つであり、標準のSQLインターフェースを独自の関数で拡張できます。たとえば、UPPER(すべての記号を大文字に設定する)などの文字列操作関数は、InterBaseセットに含まれる標準のUDFライブラリに実装されています。独自のUDFを作成できるおかげで、開発者はInterBaseの機能を実質的に任意の関数で拡張できます。UDFを作成するには、動的ライブラリを作成できる任意のプログラミング環境(Visual C++、C++ Builder、Delphiなど)を使用できます。
結論
この章では、InterBaseデータベース内でのデータの保存と処理の実装に関する質問を初めて検討しました。残念ながら、このテーマを簡単にレビューするには、多数の用語と不正確な類推に頼らざるを得ません。データベースの物理構造と論理構造をより詳細に説明する場合は、InterBaseの一次コードに言及する必要がありますが、それは別の本になるでしょう。
それでも、すべてのプログラマーが毎日使用する製品の内容に精通することは有用だと考えています。
参考文献
- 「The On-Disk Structure of InterBase」 Ann.W.Harrison著
2.「Space Management in InterBase」 Ann W.Harrison著
- 「Structure of a Data Page」 Paul Beach著(Dave Schnepper氏とDeej Bredenberg氏に感謝)