ACID 特性の記事一覧 | ||||
---|---|---|---|---|
データベースの基礎知識 | ||||
---|---|---|---|---|
リード現象 (Read Phenomena)
一般的なリード現象は次の 3 つです。
トランザクションが分離されているなら、右側は COMMIT するまで同じ値を返すべきである。
(右側は COMMIT するまで、左側のトランザクションの影響を受けないことが期待される)
リード現象の対策
リード現象を防ぐために、RDBMS には「MVCC」と「ロック」の2つの機能があります。
MVCC は、行に以下の3つのフィールドを追加することで実現します。
フィールド | 説明 |
---|---|
DB_TRX_ID | 行を挿入/更新(削除)したトランザクションの識別子 |
DB_ROLL_PTR | Undo レコードへのポインタ |
DB_ROW_ID | 行の識別子 |
MVCC では、過去のスナップショット (変更前の行) を読み取ることで、リード現象を防ぎます。
先にロックが取得されている場合は、後続はロックの解除を待つ必要があります。
ロックの範囲
ロックの範囲 (LOCK_TYPE) には、行ロックと、テーブルロックが存在します。
ロックの種類
ロックの種類 (LOCK_MODE) には、次の2種類が存在します。
ロックの種類 | 該当する SQL | 説明 |
---|---|---|
共有ロック (S) | SELECT ... FOR SHARE | 読み取りのため |
排他ロック (X) | SELECT ... FOR UPDATE/INSERT/UPDATE/DELETE | 読み書きのため |
ロックの取得条件
すでに誰かがロックを取得している場合、後から取得するロックには、次の制限があります。
先/後 | 共有ロック (S) | 排他ロック (X) |
---|---|---|
共有ロック (S) | ◯ | × |
排他ロック (X) | × | × |
つまり、誰かが読み取り中のレコードは書き込めませんし、誰かが書き込み中のレコードは読み書きできません。(ロックが解放されるまで待つ必要があります)
これにより、リード現象を防ぎます。
ロック読み取りと一貫性読み取り
ロックの解除待ちが発生するため、ロックはなるべくしたくないです。
そのため、ロックを取得しない一貫性読み取り (SELECT 文) が存在します。
読み取りの種類 | SQL | MVCC 読み取り先 | ロック |
---|---|---|---|
一貫性読み取り (Consistent Read) | SELECT | スナップショット | ロックしない |
ロック読み取り (Locking Read) | SELECT ... FOR SHARE | 現在のテーブル | 共有ロック |
SELECT ... FOR UPDATE | 現在のテーブル | 排他ロック |
一貫性読み取りでは、ロックの代わりに MVCC のスナップショットでリード現象を防ぎます。
分離レベル
トランザクションを並行で実行すると、他のトランザクションでリード現象が発生します。
一方で、パフォーマンスの観点から、トランザクションは可能な限り並行で実行したいです。
そのため、分離レベルを用いて、パフォーマンスと Isolation のバランスを決定します。
分離レベル | ダーティーリード | ノンリピータブルリード | ファントムリード |
---|---|---|---|
READ UNCOMMITTED | 発生する | 発生する | 発生する |
READ COMMITTED | 発生しない | 発生する | 発生する |
REPEATABLE READ | 発生しない | 発生しない | △※1 |
SERIALIZABLE | 発生しない | 発生しない | 発生しない |
MySQL (InnoDB) の場合は、一貫性読み取りとロック読み取りの1種類だけだと「発生しない」。組み合わせた場合は「発生する」
一貫性読み取りは MVCC, ロック読み取りはギャップロック/ネクストキーロックが防ぐ
なお、分離レベルは以下のグローバル変数・セッション変数で確認可能です。
+-----------------------+------------------------+ | @@global.tx_isolation | @@session.tx_isolation | +-----------------------+------------------------+ | REPEATABLE-READ | REPEATABLE-READ | +-----------------------+------------------------+
SERIALIZABLE は、全てのリード現象を防ぎます。
MySQL (InnoDB) の SERIALIZABLE では、すべての SELECT 文で共有ロックを取得 (SELECT ... FOR SHARE に変換) することで SERIALIZABLE を保証します。
https://dev.mysql.com/doc/refman/8.0/ja/innodb-transaction-isolation-levels.html
SERIALIZABLE
このレベルはREPEATABLE READ
と似ていますが、autocommit
が無効になっている場合、InnoDB
はすべてのプレーンSELECT
ステートメントをSELECT ... FOR SHARE
に暗黙的に変換します。autocommit
が有効な場合、SELECT
は独自のトランザクションです。
REPEATABLE READ は、ANSI の定義上ファントムリードが発生します。
しかし、MySQL (InnoDB) の場合、「一貫性読み取り」と「ロック読み取り」が混在しない場合は、ファントムリードが発生しません。(混在する場合は発生します)
読み取りの種類 | SQL | ファントムリードを防ぐ方法 |
---|---|---|
一貫性読み取り (Consistent Read) | SELECT | 最初に SELECT を実行した時の MVCC のスナップショット |
ロック読み取り (Locking Read) | SELECT ... FOR SHARE | 共有ロック (ギャップロック/ネクストキーロック) |
SELECT ... FOR UPDATE | 排他ロック (ギャップロック/ネクストキーロック) |
READ COMMITTED では、ファントムリードとノンリピータブルリードが発生します。
※MySQL の READ COMMITTED では、以下の理由でファントムリードが発生
・一貫性読み取りでは、常に最新のスナップショットを取得するため
・ロック読み取りでは、ギャップロックを行わないため
ファントムリードの動作
+------+
| id |
+------+
| 1 |
+------+
Empty set (0.00 sec)
+------+
| id |
+------+
| 1 |
+------+
ノンリピータブルリードの動作
+------+
| id |
+------+
| 2 |
+------+
+------+ | id | +------+ | 1 | +------+
+------+
| id |
+------+
| 2 |
+------+
READ UNCOMMITTED では、ファントムリードとノンリピータブルリードに加えて、ダーティリードが発生します。
ダーティリードの動作
+------+ | id | +------+ | 1 | +------+
+------+
| id |
+------+
| 1 |
+------+
最後に
関連記事
ACID 特性の記事一覧 | ||||
---|---|---|---|---|
データベースの基礎知識 | ||||
---|---|---|---|---|