基礎前提知識

テーブルはディスクにどう置かれているか(ファイル・ページ・行ID)

定義

ページとは、RDBがディスクとメモリのやり取りに使う固定サイズの単位(多くは8KB前後)で、複数行がまとめて格納される。ページは連続した数〜数十枚をまとめたエクステント単位で割り当てられ、行ID(ROWID / CTID)はページ番号とページ内オフセットの組で1行を一意に指す。

物理ストレージは 4 階層

RDBが扱うテーブルは、単なる行の一覧ではありません。実際にはディスク上でファイル → エクステント → ページ → 行の入れ子構造で保存されています。 インデックスの探索やフルスキャンの速さは、最終的にこの階層のどこにどれだけアクセスするかで決まります。

物理ストレージの階層
FILE数百MB 〜 数GB
PostgreSQL / MySQL InnoDB では 1 テーブル 1 ファイルが基本
zoomEXTENT64KB 〜 1MB
連続した複数ページの束。ディスク上の割り当て単位
zoomPAGE8KB (InnoDB: 16KB)headerrow · 2row · 3row · 4row · 5
ディスクとメモリのI/O単位。数行〜数十行が入る
zoomROW数十 〜 数百バイトid = 42name = "Sato"email = "s@…"rowid = (1, 0)
行ID (page 番号, ページ内 offset) で一意に指せる
ズームインしていく形で「ファイル → エクステント → ページ → 行」の入れ子構造を示している。RDBの物理I/Oはすべてこの階層のどこかを触っている。

用語の注意: OS/ディスク側で「ブロック」と呼ばれる単位と、DB側の「ページ」はほぼ同じ意味で使われる。Oracleだけ「データブロック」という呼び方をするが、指しているものは他DBの「ページ」と同じ。本サイトでは以降「ページ」で統一する。

ファイル: 1テーブルは基本 1ファイル

1テーブルは概ね1つのファイルにマップされます。 ただしテーブルスペースやファイルグループなどの仕組みで、1テーブルが複数ファイルにまたがる設計もあります。

エクステント: ページの束・割り当ての単位

テーブルに新しい行を挿入していくと、ページが埋まってきて追加の領域が必要になります。 このときDBは 1 ページずつではなく 連続した複数ページをまとめて確保します。 この束が エクステント です。

エクステントを意識することで、 「クラスタ化インデックスの範囲検索がなぜシーケンシャルI/Oで済むのか」が物理的に理解できます。連続するページが同一エクステント内にあれば、ディスクヘッドを大きく動かさずに読める(あるいはSSDでも先読みが効く)からです。

ページ: ディスクとメモリのやり取りの単位

DBがディスクから読むときも、書くときも、単位はいつも 1ページまるごと。 1バイトだけ欲しくても、そのバイトが入っているページ全体を読み込みます。 だから「1ページに何行入っているか」がパフォーマンスの直接的な決定要因になります。

ページと行ID
テーブル users のディスク配置
Page 1約8KB
offset 0
id=42
Sato
offset 1
id=15
Tanaka
offset 2
id=83
Suzuki
offset 3
id=27
Ito
Page 2約8KB
offset 0
id=61
Nakamura
offset 1
id=4
Yamada
offset 2
id=99
Kobayashi
offset 3
id=33
Watanabe
Page 3約8KB
offset 0
id=71
Takahashi
offset 1
id=8
Saito
offset 2
id=55
Kato
offset 3
id=19
Yoshida
用語
ページ / ブロック
ディスクI/Oの単位。1ページに複数行がまとまって入る。
行ID
(page, offset) の組。1行を一意に指すポインタ。
物理I/O
ページをディスクから読む操作。1回で数ミリ秒かかる。
状況

行ID 2:1 が指すページを 1回のI/O で読み込み、その中から目的の行を取り出す。

RDBはディスクとの間で行ではなく ページ単位(多くは8KB前後)でデータをやり取りする。1回のI/Oで1ページ = 数行〜数十行が一緒に読まれる。

行ID: 1行を指すポインタ

あるカラム値ではなく 物理的に1行を指す ためのIDが行IDです。 中身は(ページ番号, ページ内オフセット)の組。RDBMSごとに呼び方は違いますが、意味はどれも同じです。

なぜこれが重要かというと、インデックスの実体は「キー → 行ID」のマッピングだからです。 インデックスを引くと行IDが返ってきて、そのIDが指すページを1回読めば目的の行にたどり着けます。 インデックス探索が速いのは、この「1回のページ読み込みで済む」という物理的な事実に支えられています。

この理解が効いてくる場面

  • B-tree: 葉ノードから行IDを引き、その行が入っているページを1回読む。
  • クラスタ化インデックス: テーブル本体のページ並びそのものがキー順になっていて、連続エクステントを一気に読める。
  • カバリングインデックス: 必要な列がインデックス側に入っているから、テーブル本体のページを読まなくていい。
  • インデックスのコスト: 1回のランダムページ読み取りは連続読み取りの数十〜数百倍遅い、というのが更新コスト議論の前提。

よくある疑問

Q.「ページ」と「ブロック」は同じもの?
A.厳密にはOS/ディスク側のI/O単位が「ブロック」、DB側のI/O単位が「ページ」だが、実務ではほぼ同じ意味で使われる。Oracleだけ「データブロック」と呼び、それが他DBのページに相当する。本サイトでは「ページ」で統一している。
Q.1ページのサイズは何バイト?
A.PostgreSQLのデフォルトは8KB、MySQL(InnoDB)は16KB、SQL Serverは8KBが標準。RDBMSと設定によって変わるが、目安として「数KB〜数十KB」と覚えておけばよい。
Q.エクステントって毎回意識する必要ある?
A.日々のSQLチューニングでは不要。ただし「なぜ範囲検索が連続領域なら速いか」「大量ロード後にVACUUMが必要な理由」といった物理的な話題では避けて通れない概念。IPAデータベーススペシャリスト試験でも頻出。
Q.1テーブルは必ず1ファイルに入っている?
A.PostgreSQLとMySQL InnoDB (`innodb_file_per_table=ON`) では概ねYES(PostgreSQLは1GB超で自動分割)。SQLiteはDB全体で1ファイル。Oracle/SQL Serverは「テーブルスペース」「ファイルグループ」の仕組みで複数ファイルにまたがる設計もある。
Q.行IDはどう表記される?
A.PostgreSQLではCTID(例: (0,1))、Oracle/MySQL InnoDBではROWID相当のもの、SQL Serverでは(File, Page, Slot)の組。名前は違うが意味は同じで、ページ番号とページ内オフセットで1行を特定する。
Q.インデックスの葉ノードには何が入っている?
A.検索キーとそれに対応する行IDが入っている。インデックスから取得した行IDを使って、テーブル本体の該当ページを読み出す。カバリングインデックスの場合はここに追加のカラム値も含める。

関連トピック

オンライン個別指導

もっと深くDBを学びたい方へ。

たいてっくが、SQL・データベース設計・パフォーマンスチューニング・ IPAデータベーススペシャリスト対策まで、1対1で学習をサポートします。まずは無料相談から。

無料相談を予約する →