このドキュメントは疑問に思っていた点をAIエージェントとの対話を通じて整理したメモです。InnoDBテーブルがディスクに保存される構造をtablespace、segment、extent、page、rowの順で整理します。
まず前提として#
この記事は**innodb_file_per_table=ONの一般的なInnoDB非パーティションテーブル**を基準に説明する。この前提があれば、.ibdファイル1つを中心に構造を理解しやすい。
ただし以下の場合は説明が変わる。
innodb_file_per_table=OFFであれば、新しいテーブルはシステムtablespaceに保存される。- general tablespaceを使うと、複数のテーブルが1つのtablespaceを共有できる。
- パーティションテーブルは、パーティションごとに別のtablespace/fileが作られる場合がある。
各テーブルはファイル1つなのか?#
通常はそうだが、正確には「テーブルはtablespaceに保存される」が正しい。
innodb_file_per_table=ONのとき、一般的なInnoDBテーブルはsingle-table tablespaceに保存され、そのtablespaceは通常1つの.ibdファイルとして存在する。
/var/lib/mysql/mydb/
├── user.ibd
├── orders.ibd
└── product.ibdこのモードでは、.ibd1つの中にそのテーブルのクラスタードインデックス(実際の行データ)とセカンダリインデックスが一緒に入る。
核心は以下の通りだ。
Table = Fileは厳密な原理ではなく、file-per-table構成でよく見られる結果だ。- より正確な表現は
Table -> Tablespace -> backing file(.ibd)だ。
1つのファイルの中には複数のページがあるのか?#
Yes. .ibdファイルは同じサイズのdatabase pageが連続して配置された構造だ。
user.ibd
┌─────────┬─────────┬─────────┬─────────┬─────┐
│ Page 0 │ Page 1 │ Page 2 │ Page 3 │ ... │
└─────────┴─────────┴─────────┴─────────┴─────┘- pageサイズのデフォルト値は16KB
innodb_page_sizeによって4KB、8KB、16KB、32KB、64KBが可能- ディスクI/OとBuffer Poolのキャッシュも結局このpage単位で行われる
全体の階層構造#
file-per-table基準で見ると、以下の式で理解するのが最も混乱しにくい。
Table / Index
└── Tablespace
├── backing file: .ibd
└── Segment
└── Extent
└── Page
└── Record(Row)ここで重要な点は次の2つだ。
- tablespaceが上位概念であり、ファイルはそのtablespaceを収める物理的なストレージだ。
- segment / extent / pageはtablespace内部の空間管理構造だ。
各階層の詳細#
1) Tablespaceとファイル#
InnoDBはデータをまずtablespaceに配置し、そのtablespaceを1つ以上のファイルで保持する。
- single-table tablespace: テーブル1つ専用
- system tablespace: 複数の内部構造やテーブルが共有
- general tablespace: ユーザーが複数のテーブルをまとめて入れられる
- undo tablespace: undoログ用
つまり、.ibdファイルを中心に説明することはできるが、本質はファイルよりtablespaceだ。
2) Segment#
segmentはtablespace内部の論理的な保存単位だ。ここで混乱しやすいポイントがある。
- InnoDBは各インデックスごとに2つのsegmentを持つ。
- 1つはnon-leaf node用、もう1つはleaf node用だ。
- ここで言うsegmentはrollback segmentとは異なる概念だ。
つまり、rollback segmentを同じ.ibd内の一般segmentとして一緒に語ると不正確になる。
また、leaf nodeに保存される内容もインデックスの種類によって異なる。
| インデックス | leaf pageに保存されるもの |
|---|---|
| Clustered Index | 実際の行データ |
| Secondary Index | セカンダリインデックスキー + 主キーの値 |
そのため「leaf node = 実際のrow」はクラスタードインデックスに対してのみ正確な表現だ。
3) Extent#
extentはInnoDBが空間をまとめて管理する単位だ。
| Pageサイズ | Extentサイズ | 構成 |
|---|---|---|
| 4KB | 1MB | 256 pages |
| 8KB | 1MB | 128 pages |
| 16KB | 1MB | 64 pages |
| 32KB | 2MB | 64 pages |
| 64KB | 4MB | 64 pages |
補足すべき点は2つある。
- extentはtablespace内の連続したpageの束であり、ファイルシステム/ディスクの物理セクタまで必ず連続しているという意味ではない。
- segmentは最初からextent単位でのみ大きくなるわけではない。最初の32 pageは1枚ずつ、それ以降からextent単位の割り当てが始まる。
つまり「extent = 常に実際の物理ディスクで完全に連続した大きな塊」と理解すると過剰だ。ただしInnoDBはextent単位の管理を通じて**論理的な局所性(locality)**と順次アクセスの可能性を高めようとしている。
4) Page#
pageはInnoDBの基本ディスクI/O単位でありBuffer Poolキャッシュ単位だ。
正確性のために、page構造は「ページの種類によって少しずつ異なる」という前提を置くのが正しい。以下はindex page基準の単純化した図だ。
┌──────────────────────────────────┐
│ FIL Header (38 bytes) │
├──────────────────────────────────┤
│ Page Header (index page専用) │
├──────────────────────────────────┤
│ Infimum / Supremum records │
├──────────────────────────────────┤
│ User Records │
├──────────────────────────────────┤
│ Free Space │
├──────────────────────────────────┤
│ Page Directory │
├──────────────────────────────────┤
│ FIL Trailer (8 bytes) │
└──────────────────────────────────┘よく見るpage typeは以下の通りだ。
| タイプ | 役割 |
|---|---|
FIL_PAGE_INDEX | B-tree nodeページ |
FIL_PAGE_TYPE_FSP_HDR | tablespaceヘッダーページ |
FIL_PAGE_IBUF_BITMAP | change buffer / allocation bitmap |
FIL_PAGE_INODE | segment inode情報 |
FIL_PAGE_UNDO_LOG | undoログページ |
5) Row / Record#
InnoDBにおいて「row」は結局clustered index leaf recordとして保存される。
大きな可変長カラムは常に同じ方式で処理されるわけではない。
- row formatとpage sizeによって一部はページ内部、一部はoff-page/overflow pageに行く場合がある。
DYNAMIC/COMPRESSEDrow formatでは、長いカラムが20バイトのポインタだけを残して外部pageに移動する場合がある。COMPACT/REDUNDANTは先頭768バイトをインラインに置き、残りを外部pageに保存する場合がある。
つまり「BLOB/TEXTは必ずoverflow pageに行く」や「常に20バイトポインタだけが残る」と断定すると、row formatの違いを見落とすことになる。
実際のクエリ実行フロー#
SELECT * FROM user WHERE id = 5;おおまかな流れは以下の通りだ。
1. Buffer Poolに必要なpageがあるか確認
2. なければclustered indexのroot/branch/leaf pageをたどって降りる
3. 必要なpageをディスクから読み込んでBuffer Poolに載せる
4. leaf pageの中のrecordを見つけて返すここで核心は、クエリがrow単位に見えても、実際のストレージエンジンはpage単位で読んでpage単位でキャッシュするという点だ。
まとめ#
| 単位 | 性格 | 核心的な役割 |
|---|---|---|
| Table | 論理 | ユーザー視点のテーブル |
| Tablespace | 論理+物理の接続 | テーブル/インデックスが配置される保存空間 |
File (.ibd) | 物理 | single-table tablespaceのbacking file |
| Segment | 論理 | インデックスleaf/non-leafの空間管理 |
| Extent | 空間割り当て単位 | pageの束 |
| Page | I/O単位 | ディスクの読み書きとBuffer Poolキャッシュの最小単位 |
| Record(Row) | データ単位 | 実際の行レコード |
核心ポイントだけまとめると:
- 「
テーブル = ファイル」はfile-per-table環境でよく見られる結果であり、本質そのものではない。 - InnoDBは実際にtablespace -> segment -> extent -> pageの構造で空間を管理する。
- パフォーマンスの観点で最も重要な単位は結局pageだ。
