メインコンテンツへスキップ
  1. Posts/

[MySQL] InnoDBではテーブルは本当にファイル1つに保存されるのか?Tablespace、Segment、Extent、Page、Row

·7 分
NineKoo9
著者
NineKoo9
目次

このドキュメントは疑問に思っていた点を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サイズ構成
4KB1MB256 pages
8KB1MB128 pages
16KB1MB64 pages
32KB2MB64 pages
64KB4MB64 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_INDEXB-tree nodeページ
FIL_PAGE_TYPE_FSP_HDRtablespaceヘッダーページ
FIL_PAGE_IBUF_BITMAPchange buffer / allocation bitmap
FIL_PAGE_INODEsegment inode情報
FIL_PAGE_UNDO_LOGundoログページ

5) Row / Record
#

InnoDBにおいて「row」は結局clustered index leaf recordとして保存される。

大きな可変長カラムは常に同じ方式で処理されるわけではない。

  • row formatとpage sizeによって一部はページ内部、一部はoff-page/overflow pageに行く場合がある。
  • DYNAMIC / COMPRESSED row 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の束
PageI/O単位ディスクの読み書きとBuffer Poolキャッシュの最小単位
Record(Row)データ単位実際の行レコード

核心ポイントだけまとめると:

  • テーブル = ファイル」はfile-per-table環境でよく見られる結果であり、本質そのものではない。
  • InnoDBは実際にtablespace -> segment -> extent -> pageの構造で空間を管理する。
  • パフォーマンスの観点で最も重要な単位は結局pageだ。

References
#