本節ではPostgreSQLのテーブルおよびインデックスで使われるページ書式の概略について説明します。 " type="a" --> [1] " type="a" --> TOASTのテーブルとシーケンスは、通常のテーブルと同様に整形されています。
以下の説明では1バイトは8ビットからなることを前堤としています。 さらに、アイテムという単語は、ページに格納される個別のデータ値のことを指しています。 テーブル内ではアイテムは行であり、インデックス内ではアイテムはインデックスのエントリです。
テーブルとインデックスは全て、固定サイズ(通常8キロバイト。サーバのコンパイル時に異なるサイズを設定可能)のページの集まりとして格納されます。 テーブルでは、全てのページは論理上等価です。 したがって、ある項目(行)はどのページにでも格納することができます。 インデックスでは、初めのページは通常、制御用の情報を保持するメタページとして予約されます。 また、インデックスではインデックスアクセスメソッドに依存した様々なページ種類があります。
表53-2はページの全体的なレイアウトを示しています。 各ページには5つの部分があります。
表 53-2. ページレイアウト全体
アイテム | 説明 |
---|---|
PageHeaderData | 長さは24バイト。空き領域ポインタを含む、ページについての一般情報です。 |
ItemIdData | 実際のアイテムを指す(オフセットと長さの)ペアの配列です。 1アイテムにつき4バイトです。 |
空き領域 | 割り当てられていない空間です。 新規のアイテムポインタはこの領域の先頭から、新規のアイテムは最後から割り当てられます。 |
アイテム | 実際のアイテムそのものです。 |
特別な空間 | インデックスアクセスメソッド特有のデータです。異なるメソッドは異なるデータを格納します。通常のテーブルでは空です。 |
それぞれのページの最初の24バイトはページヘッダ(PageHeaderData)から構成されています。 その書式を表53-3にて説明します。 最初の2つのフィールドは、このページに関連する最も最近のWAL項目を表しています。 次にフラグビットを含む2バイトのフィールドがあります。 その後に2バイトの整数フィールドが3つ続きます(pd_lower、pd_upper、pd_special)。 これらには、割り当てられていない空間の始まり、割り当てられていない空間の終わり、そして特別な空間の始まりのバイトオフセットが格納されています。 ページヘッダの次の2バイトであるpd_pagesize_versionは、ページサイズとバージョン指示子の両方を格納します。 PostgreSQL 8.3のバージョン番号は4、PostgreSQL 8.1と8.2のバージョン番号は3、PostgreSQL 8.0のバージョン番号は2、PostgreSQL 7.3と7.4のバージョン番号は1です。 それより前のリリースのバージョン番号は0です (ほとんどのバージョン間で基本的なページレイアウトやヘッダの書式は変更されていませんが、ヒープ行ヘッダのレイアウトが変更されました)。 ページサイズは基本的に、照合用としてのみ存在しています。 同一インストレーションでの複数のページサイズはサポートされていません。 最後のフィールドはそのページの切り詰めが有益かどうかをしめすヒントです。 これはページ上で切り詰められていないもっとも古いXMAXが追跡するものです。
表 53-3. PageHeaderDataのレイアウト
フィールド | 型 | 長さ | 説明 |
---|---|---|---|
pd_lsn | XLogRecPtr | 8バイト | LSN: 最終変更に対応するxlogの最後のバイトの次のバイト |
pd_tli | uint16 | 2バイト | 最終変更のTimeLineID(その下位側16ビットのみ) |
pd_flags | uint16 | 2バイト | フラグビット |
pd_lower | LocationIndex | 2 バイト | 空き領域の始まりに対するオフセット |
pd_upper | LocationIndex | 2バイト | 空き領域の終わりに対するオフセット |
pd_special | LocationIndex | 2バイト | 特別な空間の始まりに対するオフセット |
pd_pagesize_version | uint16 | 2バイト | ページサイズおよびレイアウトのバージョン番号の情報 |
pd_prune_xid | TransactionId | 4バイト | ページ上でもっとも古い切り詰められていないXMAX。存在しなければゼロ。 |
詳細情報についてはsrc/include/storage/bufpage.hを参照してください。
ページヘッダに続くのはアイテム識別子(ItemIdData)です。 識別子ごとに4バイトを必要とします。 アイテム識別子は、アイテムが開始されるバイトオフセット、バイト単位の長さ、そしてその解釈に影響する属性ビット群を持っています。 新しいアイテム識別子は必要に応じて、未割当て空間の最初から割り当てられます。 アイテム識別子の数は、新しい識別子を割り当てるために増加されるpd_lowerを見ることで決定できます。 アイテム識別子は解放されるまで動かされることがないので、アイテム自体が空き領域をまとめるためにページ上で移動される場合でも、そのインデックスはアイテムを参照するために長期にわたって使うことができます。 実際、PostgreSQLが作る、アイテムへのポインタ(ItemPointer、CTIDとも言います)はページ番号とアイテム識別子のインデックスによって構成されています。
アイテム自体は、未割り当て空間の最後から順番に割り当てられた空間に格納されます。 正確な構造は、テーブルに何を含めるかによって異なります。 テーブルとシーケンスの両方が、以下で説明するHeapTupleHeaderDataという構造を使用します。
最後のセクションは、アクセスメソッドが格納しようとするものを何でも含めることのできる"特別なセクション"です。 例えば、B-treeインデックスは、そのページの両隣のページへのリンク、ならびに、インデックス構造体に関連したその他の何らかのデータを持ちます。 通常のテーブルではこれはまったく使用されません(ページサイズを同じにするためにpd_specialを設定することで示されます)。
テーブル行は全て、同じ方法で構成されています。 固定サイズのヘッダ(ほとんどのマシンで23バイトを占有します)があり、その後にオプションのNULLビットマップ、オプションのオブジェクトIDフィールド、およびユーザデータが続きます。 ヘッダについては表53-4で説明します。 実際のユーザデータ(行内の列)は、常にプラットフォームのMAXALIGN距離の倍数であるt_hoffで示されるオフセットから始まります。 NULLビットマップはHEAP_HASNULLビットがt_infomaskで設定されている場合にのみ存在します。 存在する場合は、固定ヘッダのすぐ後ろから始まり、データ列ごとに1ビットとするのに十分なバイト数を占有します(合計すると、t_nattsのビット数となります)。 このビットのリスト内では、1ビットは非NULLを、0ビットはNULLを示します。 このビットマップが存在しない場合、全ての列が非NULLとみなされます。 オブジェクトIDはHEAP_HASOIDビットがt_infomaskで設定されている場合にのみ存在します。 存在する場合、これはt_hoff境界の直前に現れます。 t_hoffをMAXALIGNの倍数とするために必要なパッドは全て、NULLビットマップとオブジェクトIDの間に現れます (このことにより、オブジェクトIDの位置揃えが確実に適切になります)。
表 53-4. HeapTupleHeaderDataのレイアウト
フィールド | 型 | 長さ | 説明 |
---|---|---|---|
t_xmin | TransactionId | 4バイト | 挿入XIDスタンプ |
t_xmax | TransactionId | 4バイト | 削除XIDスタンプ |
t_cid | CommandId | 4バイト | 挿入、削除の両方または片方のCIDスタンプ(t_xvacと共有) |
t_xvac | TransactionId | 4バイト | 行バージョンを移すVACUUM操作用XID |
t_ctid | ItemPointerData | 6バイト | この行または最新バージョンの行の現在のTID |
t_infomask2 | int16 | 2バイト | 属性の数と各種フラグビット |
t_infomask | uint16 | 2バイト | 様々なフラグビット |
t_hoff | uint8 | 1バイト | ユーザデータに対するオフセット |
詳細情報についてはsrc/include/access/htup.hを参照してください。
実際のデータの解釈は、他のテーブル、ほとんどの場合、pg_attributeから取得された情報でのみ行うことができます。 フィールド位置を識別するために必要なキー値は、attlenおよびattalignです。 フィールドの幅が固定されていてNULL値が存在しない場合を除き、特定の属性を直接取得する方法はありません。 この仕組みは全て、heap_getattr、fastgetattrおよびheap_getsysattr関数にラップされています。
データを読むためには、それぞれの属性を順番に検査する必要があります。 まず、NULLビットマップに従ってフィールドがNULLかどうかをチェックします。 もしNULLであれば、次に進みます。 次に、位置揃えが正しいことを確認してください。 フィールドの幅が固定されていれば、全てのバイトが単純に配置されます。 可変長のフィールド(attlen == -1)の場合はもう少し複雑です。 可変長のデータ型は全て、格納する値の長さといくつかのフラグビットを持つstruct varlenaという共通ヘッダ構造体を共有します。 フラグによって、データは行内、または別のテーブル(TOAST)のいずれかとなったり、圧縮済みとなったりします (項53.2を参照してください)。
[1] | 実際にはインデックスアクセスメソッドはこのページ書式を使用する必要はありません。 既存の全てのインデックスメソッドがこの基本書式を使用しています。 しかし、インデックスメタページに保持されるデータは通常、アイテムレイアウト規則に正確には従っていません。 |