[InterDB] [著者HP] [PREVIOUS][UP][NEXT]

Copyright @ 2009, Suzuki Hironobu @ InterDB


■2-08■ InnoDB型

テーブルスペース

InnoDB型テーブルのデータはシステム変数`innodb_data_file_path'に設定された(複数の)ファイルに分割して保管されます。
MySQLサーバはこれらの物理ファイルをまとめて、`テーブルスペース(Table Space)'という論理的な構造として扱います(【図.2-15】参照)。

図. 2-15 テーブルスペース

テーブルスペースは`インデックス'、`ロールバックセグメント'、および`テーブルデータ'から成ります。
ここではインデックスとロールバックセグメントについて説明します。

インデックス

InnoDBは各テーブル毎に2つのインデックス(【補足 1】,【補足 2】)を持ち、プライマリーインデックスとセカンダリーインデックスとよびます。

プライマリーインデックスは、テーブルの主キー(PRIMARY KEY)か一意キー(UNIQUE KEY)をもとに作成されます。テーブルに主キーや一意キーがない場合はMySQLが内部的にキーを割り当てます。

セカンダリーインデックスは、プライマリーインデックスで使った主キーか一意キー以外のインデックスについて作成されます。


ロールバックセグメント

データベースの一貫性を保つ機構は'ロールバックセグメント'によって実現しています(【図.2-16】参照)。

図.2-16 ロールバックセグメント

`テーブルデータ'には文字通りInnoDB型テーブルのデータが保存されています。各テーブルデータには`トランザクションID'と`ロールバックセグメントへのポインタ'の、2つの属性が付与されます。

`トランザクションID'は6[byte]長で、各トランザクションに一意な値が割り当てられます(補足 2)。

`ロールバックセグメント'は`UNDOログ'ともよばれ、データの更新,挿入,削除が行われた際、過去のデータを保存する領域です(補足 3)。よって`ロールバックセグメントへのポインタ'とは(変更前の)過去のデータへのポインタということです。


以下、具体例を使ってロールバックセグメントの仕組みを説明します(【図.2-17】参照)。

図.2-17 ロールバックセグメントの仕組み

データベースユーザAとデータベースユーザBが相次いでトランザクション処理を開始します。トランザクションID(TXID)はそれぞれ1233と1234です。

データベースユーザBがテーブルの更新を行うと、テーブルデータのデータが更新され、過去のデータはロールバックセグメントに保存されます。

この時点でデータベースユーザAがテーブルの検索を行うと、(ポインタを辿って)ロールバックセグメント内の更新前のデータが参照されます。このように、ロールバックセグメントのデータを使ってトランザクション間の一貫性を実現するのです。

双方のトランザクションがコミットするとロールバックセグメントのデータは消去されます。逆に、ロールバックするとロールバックセグメントのデータは再度テーブルデータに書き戻された後、消去されます。

なお、MVCCの特徴はREADロックの獲得と、WRITEロックの獲得が競合しないことです。
よって、データ操作(INSERT, UPDATE, DELETE)はデータ検索(SELECT)の実行をブロックしませんし、逆もまた然りです。


ログ法とクラッシュリカバリ

MySQLにおけるトランザクションログ(とテーブルデータ)の書き込みは、WAL(Write Ahead Logging)方式が採用されています(補足 4)。
また、ファジーチェックポイント(fazzy checkpoint)による効率的なクラッシュリカバリをサポートしています。

以下、WALによるトランザクションログとテーブルデータの書き込み手順と、クラッシュリカバリの概要を解説します。

MySQLのWAL機構

WALの基本戦略は次のとおりです。

(a)データの変更はトランザクションログに書き込み、
(b)テーブルデータファイルを直接操作することは極力避ける。

まず(b)から説明しましょう。

テーブルデータファイルの直接操作を避ける理由は、ディスクアクセスのオーバーヘッド増加による性能低下を防ぐためです。テーブルデータファイルへのアクセスは(ハードディスクのレベルで)基本的にランダムアクセスになるので、シークタイムの増加は避けられません。また、テーブルデータの書き込み自体も時間を要する処理です。
そこで、テーブルデータをメモリ上にキャッシュする`buffer_pool'を用意し、データ操作は原則として`buffer_pool'上で行います。そして、一定周期で行われるチェックポイント時にのみ、メモリ上のデータをハードディスク上のテーブルデータに書き込みます。

しかし、`buffer_pool'だけでデータ操作を行うと、ハードディスクへの書き込み前になんらかの障害が発生した場合、メモリ上のデータとテーブルデータの間で不一致が生じ、データベースが破壊的ダメージを受けます。


このような事態を避けるため、基本戦略(a)のトランザクションログを使います。トランザクションログはREDOログとも呼ばれ、データベースの操作記録のようなものです。
SQL文の実行毎にメモリ上の`log_buffer'にトランザクションログを書き込み、さらにハードディスク上のトランザクションログファイルに書き込みます。トランザクションログファイルへの書き込みはシーケンシャルに行うので(ランダムアクセスとなるテーブルデータファイルへの書き込みと比較して)高速に実行できます。


以上を踏まえ、MySQLにおけるWALの概略を説明します(【図.2-18】参照)。

図.2-18 WAL

    (1) トランザクションログを`log_buffer'に書き込んだ後、`buffer_pool'を更新する
    (2)`log_buffer'のトランザクションログをハードディスク上のトランザクションログに書き込む。書き込むタイミングは 3パターンある(後述)。
    (3)一定周期でチェックポイントが実行され、`log_buffer'と`buffer_pool'の内容をハードディスク(トランザクションログとテーブルデータ)に書き込む。

テーブルデータへの書き込みは、`buffer_pool'が溢れた場合(補足 5)を除き、チェックポイント時にしか行われないことに注目してください。


トランザクションログの書き込みタイミング

`log_buffer'からハードディスクへのトランザクションログの書き込みタイミングは、システム変数`innodb_flush_log_at_trx_commit'によって制御でき、現在のところ 3パターンが選択できます(【図.2-19】, 【表.2-6】参照)。


図.2-19 トランザクションログの書き込みタイミング

表.2-6 トランザクションログの書き込みタイミング
innodb_flush_log_at_trx_commit説明
1SQL文のコミット(COMMIT)直後、ハードディスクに`同期書き込み'する。
具体的には、システムコール`write()'後にシステムコール`fdatasync()'を実行し、確実にハードディスクにデータを書き込む。バージョン4.0.13からのデフォルト動作。
01秒周期でハードディスクに`同期書き込み'する。
具体的には、1秒毎にシステムコール`write() + fdatasync()'を実行する。バージョン4.0.12までのデフォルト動作。
2SQL文のコミット(COMMIT)直後にハードディスクに`非同期書き込み'し、1秒周期でシステムコール`fdatasync()'を実行する。

デフォルトの設定以外、障害が発生した場合に最悪1秒間のトランザクションがハードディスクに書き込まれない可能性があります(【補足 8】)。


実行スレッド数の制限とInnoDBキュー

InnoDB型テーブルの処理を行なうスレッドはInnoDBキューによって管理されています。


むやみに多数のスレッドを実行しようとすると全体の性能が低下する場合が多々あります。そのような事態への対処として、システム変数innodb_thread_concurrencyで同時に実行できるスレッドの最大数を設定できるようになりました。
それ以上の数のスレッドがある場合はInnoDBキュー(FIFO)で実行待ちします。


実行中のスレッドはシステム変数innodb_concurrency_ticketsで与えられる値を保持していて、処理を行なう度にその値から1を減じていきます。innodb_concurrency_ticketsが0になるまでは実行する権利を有するわけです。


innodb_concurrency_ticketsが0になったスレッドは、システム変数innodb_thread_sleep_delayで設定された時間だけ待ってInnoDBキューに加わります。
実行可能なスレッドに空きができるとInnoDBキューで待っていたスレッドが取り出されます。


クラッシュリカバリ

最後にクラッシュリカバリについて説明します(【図.2-20】参照)。

図.2-20 クラッシュリカバリ

いくつかのSQL文が実行された直後、システムがクラッシュしたとします。ここでMySQLを再起動すると、復旧処理が始まります。

ハードディスクには最後のチェックポイント時でのデータが保存されており、それ以降の変更はトランザクションログにだけ保存されています。
そこで、トランザクションログに記録されたSQL文を順に再実行(REDO)することで、クラッシュ直前の状態にまで復旧できます。


補足

(2)
6[byte](= 48[bit])で表現できる値は`2の48乗'、つまり約281兆です。これは事実上"無限"といえるでしょう。何故なら、例えば毎秒1000トランザクション処理し続けても、6[byte]のトランザクションIDを使い切るには8925年かかるからです。

(3)
正確には2つの領域(挿入ロールバックセグメントと更新ロールバックセグメント)があります。

(4)
WALは、OracleやPostgreSQLなど現代的なDBMSのほとんどが採用しているロギング方式です。

(5)
『`buffer_pool'が溢れた場合』とは、具体的には『ダーティページ(dirty page)がシステム変数`innodb_max_dirty_pages_pct'に設定されたパーセンテージ(デフォルトは`90'[%])を越えた場合』のことです。



[PREVIOUS][UP][NEXT]