MySQLのMVCC:マルチバージョン同時実行制御の仕組み
1.1. 基本概念
MVCC(マルチバージョン同時実行制御)
MVCC(Multi-Version Concurrency Control)は、データベース管理システムにおける同時実行制御の手法の一つです。MySQLでは、サーバーレベルではなくInnoDBストレージエンジンによって実装されています。
スナップショット読み取り
ロックをかけないSELECT操作はスナップショット読み取りと呼ばれます。これは非ブロッキングの読み取り操作です。スナップショット読み取りは、直列化レベル以外の隔離レベルで有効になります。MVCCに基づいて実装されており、データの最新バージョンではなく、過去の履歴バージョンを読み取る可能性があります。
現在読み取り
SELECT FOR UPDATEやUPDATE、INSERT、DELETEなどの操作は現在読み取りと呼ばれます。これらの操作はレコードの最新バージョンを読み取り、同時に他のトランザクションによる変更をブロックするためにロックをかけます。
ファントムリード
トランザクションAが特定の条件でデータを読み取っている間に、トランザクションBが同じ検索条件に一致する新しいデータを挿入すると、トランザクションAが再度同じ条件で読み取った際に、トランザクションBが挿入した新しいデータが見つかる現象をファントムリードと呼びます。ファントムリードは主に新規挿入データに特有の問題です。
1.2. ファントムリードが発生するケース
スナップショット読み取りのみを使用する場合
MVCCメカニズムにより、各トランザクションは開始時に固定されたバージョンのデータを読み取るため、ファントムリードは発生しません。
現在読み取りのみを使用する場合
MySQLのギャップロックにより、後続のトランザクションは前のトランザクションによってブロックされ、直列実行に退化します。この場合もファントムリードは発生しません。
現在読み取りとスナップショット読み取りを混在させる場合
この混在パターンではファントムリードが発生する可能性があります。
1.3. 結論
可重复読み取り(REPEATABLE READ)隔離レベルでは、一つのトランザクション内で現在読み取りのみ、またはスナップショット読み取りのみを使用すればファントムリードを避けることができます。しかし、両者を混在させると、ファントムリードの保証は失われます。
1.4. トランザクションのACID特性
// 原子性
原子性:トランザクション内のすべての操作は、すべて成功してコミットされるか、すべて失敗してロールバックされる
// 一貫性
一貫性:常に一貫した状態から別の一貫した状態へ移行し、中間状態は存在しない
// 隔離性
隔離性:トランザクションがコミットされるまで、その変更は他のトランザクションから見えない
// 耐久性
耐久性:トランザクションがコミットされると、その変更は永続的にデータベースに保存され、システムクラッシュ後もデータは失われない
1.5. トランザクションの隔離レベル
// 未コミット読み取り(READ UNCOMMITTED)
未コミット読み取り:コミットされていない変更も他のトランザクションから見える。ダーティリードが発生する可能性あり
// コミット読み取り(READ COMMITTED)
コミット読み取り:コミットされた変更のみが見える。不可逆読み取り(non-repeatable read)が発生する可能性あり
// 可重复読み取り(REPEATABLE READ)
可重复読み取り:同一トランザクション内での複数回読み取りで一貫した結果が保証される。ファントムリードが発生する可能性あり
// シリアライザブル(SERIALIZABLE)
シリアライザブル:読み取り時に各行にロックをかけ、同時実行性が低下する可能性がある
1.6. ロックの種類
共有ロック(読み取りロック)
複数のクライアントが同時に同じリソースを読み取ることができ、互いに干渉しません。
排他ロック(書き込みロック)
一つの書き込みロックは、他の書き込みロックと読み取りロックをブロックします。
1.7. ロック粒度
可能な限り変更が必要な部分のみをロックし、不要なデータまでロックしないことが重要です。ロックされるデータ量が少ないほど、システムの同時実行性は高まります。
テーブルロック
テーブル全体をロックします。書き込み操作を行う前に書き込みロックを取得する必要があり、他のユーザーのすべての読み書き操作をブロックします。
行ロック
最大限の同時実行性をサポートし、InnoDBストレージエンジンのみで実装されています。
1.8. InnoDBのMVCC実装
InnoDBのMVCCは、各行のレコードの後に2つの隠し列を保存することで実現されています。これらの列は、行の作成時刻と削除時刻を保存しており、実際の時刻ではなくシステムバージョン番号です。新しいトランザクションが開始されるたびに、システムバージョン番号が自動的にインクリメントされます。トランザクション開始時のシステムバージョン番号がトランザクションのバージョン番号として使用され、各レコードのバージョン番号と比較されます。MVCCは可重复読み取りとコミット読み取りの2つの隔離レベルでのみ機能します。
可重复読み取りレベルでのMVCCの動作
トランザクションが開始されると、そのトランザクションのバージョン番号が記録されます。SELECT操作が実行されると、InnoDBは次のルールに基づいて適切なバージョンの行を選択します:
- 行の作成バージョンがトランザクションのバージョン番号以下であること
- 行の削除バージョンが存在しないか、またはトランザクションのバージョン番号より大きいこと
この仕組みにより、各トランザクションは一貫したデータビューを維持しながら、他のトランザクションの進行をブロックすることなく操作を実行できます。