MySQLにおけるファントムリード
作成日時:2025-01-26
更新日時:2025-01-26
※あくまで自分の解釈。正しいとは限らない。
なぜREPEATABLE READでファントムリードが発生しないか
MVCCとギャップロックのおかげ。
- MVCC:スナップショット(Undoログから自身のトランザクションIDより古いデータを取得)を参照するため
- ギャップロックとネクストキーロックにより、データを挿入/削除させないため
基本的には、MVCCによりファントムリードは発生しない。
下記のパターン3などはMVCCだけでは不十分でロックが必要になる。
パターン1:
1. トランザクションA:SELECT * FROM tbl;
2. トランザクションB:行挿入してコミット。
3. トランザクションA:再度SELECT * FROM tbl;
2で挿入したデータは見えない
ファントムリードは発生しない。
3のSELECTは1の時のスナップショットを参照しているから。
パターン2:
1. トランザクションA:SELECT * FROM tbl WHERE (インデックスを使用した範囲検索) FOR UPDATE;
2. トランザクションB:行挿入はギャップロックで失敗
登録できないのでファントムリードは発生しない。
パターン3:
1. トランザクションA:SELECT * FROM tbl;
2. トランザクションB:行挿入してコミット。
3. トランザクションA:SELECT * FROM tbl FOR UPDATE;
2で挿入したデータが見えてしまう。
4. トランザクションA:SELECT * FROM tbl;
3でファントムリードが発生する。
4は1と同じ内容、つまり2で挿入したデータは見えない。
MySQL固有の仕様らしい。
パターン3に関して
15.7.2.4 読取りのロックに下記の記載がある。
一貫性読み取りでは、読み取られたビュー内に存在するレコードに設定されたロックはすべて無視されます。 (古いバージョンのレコードはロックできません。レコードのインメモリーコピー上の Undo ログに適用することで、再構築されます。)
どういう意味だ?(機械翻訳らしいからよくわからん、後で原文を読む)
以下、推察。
- REPEATABLE READはスナップショットを見ます
- トランザクション開始時点のデータを一貫して読み取ります
- 対象データがロックされていても、スナップショットは関係ないので(スナップショットの)データを読むことは出来ます
- 古いバージョンのレコードはロックできないので、ロックするには最新データを読まねばならない
- これにより、3において2のデータが参照できてしまった?
参考
- MySQL :: MySQL 8.0 リファレンスマニュアル :: 15.7.1 InnoDB ロック
- MySQL :: MySQL 8.0 リファレンスマニュアル :: 15.7.2.1 トランザクション分離レベル
- MySQL :: MySQL 8.0 リファレンスマニュアル :: 15.7.2.4 読取りのロック
- MySQL :: MySQL 8.0 リファレンスマニュアル :: 15.7.4 ファントム行
- MySQL インデックス、MVCCの仕組み #MySQL - Qiita
- 漢(オトコ)のコンピュータ道: InnoDBのREPEATABLE READにおけるLocking Readについての注意点