本節ではPostgreSQLのテーブルおよびインデックスで使われるページ書式の概略について説明します。 [15] TOASTのテーブルとシーケンスは、通常のテーブルと同様に整形されています。
以下の説明では1バイトは8ビットからなることを前提としています。 さらに、アイテムという単語は、ページに格納される個別のデータ値のことを指しています。 テーブル内ではアイテムは行であり、インデックス内ではアイテムはインデックスのエントリです。
テーブルとインデックスはすべて、固定サイズ(通常8キロバイト。サーバのコンパイル時に異なるサイズを設定可能)のページの集まりとして格納されます。 テーブルでは、すべてのページは論理上等価です。 したがって、あるアイテム(行)はどのページにでも格納することができます。 インデックスでは、初めのページは通常、制御用の情報を保持するメタページとして予約されます。 また、インデックスではインデックスアクセスメソッドに依存した様々なページ種類があります。
表 68.2はページの全体的なレイアウトを示しています。 各ページには5つの部分があります。
表68.2 ページレイアウト全体
アイテム | 説明 |
---|---|
PageHeaderData | 長さは24バイト。空き領域ポインタを含む、ページについての一般情報です。 |
ItemIdData | 実際のアイテムを指すアイテム識別子の配列です。 各項目は(オフセットと長さの)ペアです。 1アイテムにつき4バイトです。 |
空き領域 | 割り当てられていない空間です。 新規のアイテム識別子はこの領域の先頭から、新規のアイテムは最後から割り当てられます。 |
アイテム | 実際のアイテムそのものです。 |
特別な空間 | インデックスアクセスメソッド特有のデータです。異なるメソッドは異なるデータを格納します。通常のテーブルでは空です。 |
それぞれのページの最初の24バイトはページヘッダ(PageHeaderData
)から構成されています。
その書式を表 68.3にて説明します。
最初のフィールドは、このページに関連する最も最近のWAL項目を表しています。
2番目のフィールドにはdata checksumsが有効な場合にページチェックサムが格納されています。
次にフラグビットを含む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が追跡するものです。
表68.3 PageHeaderDataのレイアウト
フィールド | 型 | 長さ | 説明 |
---|---|---|---|
pd_lsn | PageXLogRecPtr | 8バイト | LSN: このページへの最終変更に対応するWALレコードの最後のバイトの次のバイト |
pd_checksum | uint16 | 2バイト | ページチェックサム |
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
を設定することで示されます)。
図 68.1は、これらの部分がページ内でどのようにレイアウトされているかを図解しています。
図68.1 ページレイアウト
テーブル行はすべて同じ方法で構成されています。
固定サイズのヘッダ(ほとんどのマシンで23バイトを占有します)があり、その後にオプションのNULLビットマップ、オプションのオブジェクトIDフィールド、およびユーザデータが続きます。
ヘッダについては表 68.4で説明します。
実際のユーザデータ(行内の列)は、常にプラットフォームのMAXALIGN距離の倍数であるt_hoff
で示されるオフセットから始まります。
NULLビットマップはHEAP_HASNULLビットがt_infomask
で設定されている場合にのみ存在します。
存在する場合は、固定ヘッダのすぐ後ろから始まり、データ列ごとに1ビットとするのに十分なバイト数を占有します(すなわち、t_infomask2
内の属性の個数と等しいビット数です)。
このビットのリスト内では、1ビットは非NULLを、0ビットはNULLを示します。
このビットマップが存在しない場合、すべての列が非NULLとみなされます。
オブジェクトIDはHEAP_HASOID_OLDビットがt_infomask
で設定されている場合にのみ存在します。
存在する場合、これはt_hoff
境界の直前に現れます。
t_hoff
をMAXALIGNの倍数とするために必要なパッドは全て、NULLビットマップとオブジェクトIDの間に現れます
(このことにより、オブジェクトIDの位置揃えが確実に適切になります)。
表68.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 | uint16 | 2バイト | 属性の数と各種フラグビット |
t_infomask | uint16 | 2バイト | 様々なフラグビット |
t_hoff | uint8 | 1バイト | ユーザデータに対するオフセット |
詳細情報についてはsrc/include/access/htup_details.h
を参照してください。
実際のデータの解釈は、他のテーブル、ほとんどの場合、pg_attribute
から取得された情報でのみ行うことができます。
フィールド位置を識別するために必要なキー値は、attlen
およびattalign
です。
フィールドの幅が固定されていてNULL値が存在しない場合を除き、特定の属性を直接取得する方法はありません。
この仕組みはすべて、heap_getattr、fastgetattrおよびheap_getsysattr関数にラップされています。
データを読むためには、それぞれの属性を順番に検査する必要があります。
まず、NULLビットマップに従ってフィールドがNULLかどうかを検査します。
もしNULLであれば、次に進みます。
次に、位置揃えが正しいことを確認してください。
フィールドの幅が固定されていれば、すべてのバイトが単純に配置されます。
可変長のフィールド(attlen == -1)の場合はもう少し複雑です。
可変長のデータ型はすべて、格納する値の長さといくつかのフラグビットを持つstruct varlena
という共通ヘッダ構造体を共有します。
フラグによって、データは行内、または別のテーブル(TOAST)のいずれかとなったり、圧縮済みとなったりします
(68.2を参照してください)。
[15]
実際には、テーブルアクセスメソッドもインデックスアクセスメソッドも、このページ書式を使用する必要はありません。
heap
テーブルアクセスメソッドは常にこの書式を使用します。
既存のすべてのインデックスメソッドも、この基本書式を使用しています。しかし、インデックスメタページに保持されるデータは通常、アイテムレイアウト規則に従っていません。