本節ではPostgreSQLのテーブルおよびインデックスで使われるページ書式の概略について説明します。 (インデックスアクセスメソッドはこのページ書式を使用する必要はありません。 現在では、すべてのインデックスメソッドがこの基本書式を使用しています。しかし、インデックスメタページに保持されるデータは通常、アイテムレイアウト規則に正確には従っていません。) TOASTのテーブルとシーケンスは、通常のテーブルと同様に整形されています。
以下の説明では1バイトは8ビットからなることを前堤としています。 更に、アイテムという単語は、ページに格納される個別のデータ値のことを指しています。 テーブル内では、アイテムは行であり、インデックス内では、アイテムはインデックスのエントリです。
表51-1はページの基本レイアウトを示しています。 各ページには5つの部分があります。
表 51-1. サンプルページレイアウト
アイテム | 説明 |
---|---|
PageHeaderData | 長さは20バイト。空き領域ポインタを含む、ページについての一般情報です。 |
ItemPointerData | 実際のアイテムを指す(オフセットと長さの)ペアの配列です。 |
空き領域 | 割り当てられていない空間です。 すべての新規行はここから割り当てられます(通常は最後から)。 |
アイテム | 実際のアイテムそのものです。 |
特別な空間 | インデックスアクセスメソッド特有のデータです。異なるメソッドは異なるデータを格納します。通常のテーブルでは空です。 |
それぞれのページの最初の20バイトはページヘッダ(PageHeaderData)から構成されています。 その書式を表51-2にて説明します。 最初の2つのフィールドはWALに関連する項目を扱います。 その後に、2バイトの整数フィールドが3つ続きます(pd_lower、pd_upper、pd_special)。 これらは、割り当てられていない空間の始まり、割り当てられていない空間の終わり、そして特別な空間の始まりのバイトオフセットを表しています。
表 51-2. PageHeaderData のレイアウト
フィールド | 型 | 長さ | 説明 |
---|---|---|---|
pd_lsn | XLogRecPtr | 8バイト | LSN: xlogの最後のバイトの次のバイト |
pd_sui | StartUpID | 4バイト | 最終変更のSUI(現在ではヒープAMでのみ使用) |
pd_lower | LocationIndex | 2 バイト | 空き領域の始まりに対するオフセット |
pd_upper | LocationIndex | 2バイト | 空き領域の終わりに対するオフセット |
pd_special | LocationIndex | 2バイト | 特別な空間の始まりに対するオフセット |
pd_pagesize_version | uint16 | 2バイト | ページサイズおよびレイアウトのバージョン番号の情報。 |
詳細情報についてはsrc/include/storage/bufpage.hを参照してください。
特別な空間は、ページ初期化時にページの最後に割り当てられる区域で、アクセスメソッド特有の情報を持っています。 ページヘッダの最後の2バイトであるpd_pagesize_versionは、ページサイズとバージョン支持子の両方を格納します。 PostgreSQL 7.3以降のバージョン番号は1です。 それより前のリリースのバージョン番号は0です。 (基本的なページレイアウトやヘッダの書式は変更されていませんが、ヒープ行ヘッダのレイアウトが変更されました。) ページサイズは基本的に、照合用としてのみ存在しています。 同一インストレーションでの複数のページサイズはサポートされていません。
ページヘッダに続くのはアイテム識別子(ItemIdData)です。 識別子ごとに4バイトを必要とします。 アイテム識別子は、アイテムが開始されるバイトオフセット、バイト単位の長さ、そしてその解釈に影響する属性ビット群を持っています。 新しいアイテム識別子は必要に応じて、未割当て空間の最初から割り当てられます。 アイテム識別子の数は、新しい識別子を割り当てるために増加されるpd_lowerを見ることで決定できます。 アイテム識別子は解放されるまで動かされることがないので、アイテム自体が空き領域をまとめるためにページ上で移動される場合でも、そのインデックスはアイテムを参照するために長期にわたって使うことができます。 実際、PostgreSQLが作る、アイテムへのポインタ(ItemPointer、CTIDともいいます)はページ番号とアイテム識別子のインデックスによって構成されています。
アイテム自体は、未割当て空間の最後から順番に割り当てられた空間に格納されます。 正確な構造は、テーブルに何を含めるかによって異なります。 テーブルとシーケンスの両方が、以下で説明するHeapTupleHeaderDataという構造を使用します。
最後のセクションは、アクセスメソッドが格納しようとするものを何でも含めることのできる"特別なセクション"です。 通常のテーブルでは、これは全く使用されません(ページサイズを同じにするためにpd_specialを設定することで示されます)。
テーブル行はすべて、同じ方法で構成されています。 固定サイズのヘッダ(ほとんどのマシンで23バイトを占有します)があり、その後にオプションのヌルビットマップ、オプションのオブジェクトIDフィールド、およびユーザデータが続きます。 ヘッダについては表51-3で説明します。 実際のユーザデータ(行内の列)は、常にプラットフォームのMAXALIGN距離の倍数であるt_hoffで示されるオフセットから始まります。 ヌルビットマップは HEAP_HASNULLビットがt_infomaskで設定されている場合にのみ存在します。 存在する場合は、固定ヘッダのすぐ後ろから始まり、データ列ごとに1ビットとするのに十分なバイト数を占有します(合計すると、t_nattsのビット数となります)。 このビットのリスト内では、1ビットは非ヌルを、0ビットはヌルを示します。 このビットマップが存在しない場合、すべての列が非ヌルとみなされます。 オブジェクトIDはHEAP_HASOIDビットがt_infomaskで設定されている場合にのみ存在します。 存在する場合、これはt_hoff境界の直前に現れます。 t_hoffをMAXALIGNの倍数とするために必要なパッドはすべて、ヌルビットマップとオブジェクトIDの間に現れます。 (このことにより、オブジェクトIDの位置揃えが確実に適切になります。)
表 51-3. HeapTupleHeaderDataのレイアウト
フィールド | 型 | 長さ | 説明 |
---|---|---|---|
t_xmin | TransactionId | 4バイト | XIDスタンプを挿入 |
t_cmin | CommandId | 4バイト | CIDスタンプを挿入(t_xmaxとオーバーレイ) |
t_xmax | TransactionId | 4バイト | XIDスタンプを削除 |
t_cmax | CommandId | 4バイト | CIDスタンプを削除(t_xvac とオーバーレイ) |
t_xvac | TransactionId | 4バイト | 行バージョンを移すVACUUM操作用XID |
t_ctid | ItemPointerData | 6バイト | この行または最新バージョンの行の現在のTID |
t_natts | 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であれば、次に進みます。 次に、位置揃えが正しいことを確認してください。 フィールドの幅が固定されていれば、すべてのバイトが単純に配置されます。 可変長のフィールド(attlen == -1)の場合はもう少し複雑で、varattrib可変長構造体が使用されます。 フラグによって、データは行内、圧縮済み、または別のテーブル(TOAST)のいずれかとなります。