一貫性読み取りとは、InnoDB
がマルチバージョンを使用して、ある時点でのデータベースのスナップショットをクエリーに提供することを意味します。 クエリーには、その時点よりも前にコミットされたトランザクションによる変更のみが表示され、その時点よりもあとのトランザクションまたはコミットされていないトランザクションによる変更は表示されません。 このルールの例外として、同じトランザクション内の以前のステートメントによる変更はクエリーに表示されます。 この例外によって、次のような異常が発生します。テーブル内の一部の行を更新すると、更新された行の最新バージョンが SELECT
に表示されますが、いずれかの行の旧バージョンも表示される可能性があります。 その他のセッションで同じテーブルが同時に更新される場合、その異常は、データベースに存在しない状態でテーブルが表示される可能性があることを意味します。
トランザクション分離レベルが REPEATABLE READ
(デフォルトのレベル) である場合は、同じトランザクション内のすべての一貫性読み取りで、そのトランザクション内の最初のこのような読み取りで確立されたスナップショットが読み取られます。 現在のトランザクションをコミットしたあとに、新しいクエリーを発行すると、クエリーの新しいスナップショットを取得できます。
分離レベルが READ COMMITTED
の場合は、トランザクション内の各一貫性読み取りで、独自の新しいスナップショットが設定され、読み取られます。
一貫性読み取りは、InnoDB
が READ COMMITTED
および REPEATABLE READ
分離レベルで SELECT
ステートメントを処理する際のデフォルトモードです。 一貫性読み取りではアクセスされるテーブル上にロックが設定されないため、その他のセッションも、そのテーブル上で一貫性読み取りが実行されるときと同時に、それらのテーブルを自由に変更できます。
デフォルトの REPEATABLE READ
分離レベルで実行していると仮定します。 一貫性読み取り (つまり、通常の SELECT
ステートメント) を発行すると、InnoDB
は、クエリーがデータベースを参照するときの基準となるタイムポイントをトランザクションに付与します。 タイムポイントが割り当てられたあとに、別のトランザクションが行を削除してコミットした場合は、その行が削除済みとして表示されません。 挿入および更新も同様に処理されます。
データベースの状態のスナップショットは、トランザクション内の SELECT
ステートメントに適用されますが、DML ステートメントには必ずしも適用されるとは限りません。 一部の行を挿入または変更してから、そのトランザクションをコミットする場合は、そのセッションでクエリーが実行される可能性がない場合でも、別の並列実行 REPEATABLE READ
トランザクションから発行された DELETE
または UPDATE
ステートメントによって、コミットされたばかりの行が影響を受ける可能性があります。 トランザクションによって別のトランザクションでコミットされた行が更新または削除されると、これらの変更を現在のトランザクションに表示できるようになります。 たとえば、次のような状況が発生する可能性があります。
SELECT COUNT(c1) FROM t1 WHERE c1 = 'xyz';
-- Returns 0: no rows match.
DELETE FROM t1 WHERE c1 = 'xyz';
-- Deletes several rows recently committed by other transaction.
SELECT COUNT(c2) FROM t1 WHERE c2 = 'abc';
-- Returns 0: no rows match.
UPDATE t1 SET c2 = 'cba' WHERE c2 = 'abc';
-- Affects 10 rows: another txn just committed 10 rows with 'abc' values.
SELECT COUNT(c2) FROM t1 WHERE c2 = 'cba';
-- Returns 10: this txn can now see the rows it just updated.
トランザクションをコミットしてから、別の SELECT
または START TRANSACTION WITH CONSISTENT SNAPSHOT
を実行すると、タイムポイントを進めることができます。
これは、マルチバージョン並列処理制御と呼ばれます。
次の例では、セッション B が挿入をコミットし、セッション A も同様にコミットした場合にのみ、B によって挿入された行が A に表示されます。これにより、タイムポイントが B のコミットよりも先に進みます。
Session A Session B
SET autocommit=0; SET autocommit=0;
time
| SELECT * FROM t;
| empty set
| INSERT INTO t VALUES (1, 2);
|
v SELECT * FROM t;
empty set
COMMIT;
SELECT * FROM t;
empty set
COMMIT;
SELECT * FROM t;
---------------------
| 1 | 2 |
---------------------
データベースの「最新」状態を確認する場合は、READ COMMITTED
分離レベルとロック読み取りのいずれかを使用します。
SELECT * FROM t FOR SHARE;
分離レベルが READ COMMITTED
の場合は、トランザクション内の各一貫性読み取りで、独自の新しいスナップショットが設定され、読み取られます。 FOR SHARE
では、かわりにロック読取りが発生: SELECT
は、最新の行を含むトランザクションが終了するまでブロックされます (セクション15.7.2.4「読取りのロック」 を参照)。
特定の DDL ステートメントでは、一貫性読み取りが機能しません。
DROP TABLE
では、MySQL が削除されたテーブルを使用できず、そのテーブルはInnoDB
によって破棄されるため、一貫性読み取りが機能しません。読取り一貫性は、元のテーブルの一時コピーを作成し、一時コピーの作成時に元のテーブルを削除する
ALTER TABLE
操作では機能しません。 トランザクション内で一貫性読み取りを再発行しても、新しいテーブル内の行はトランザクションのスナップショット取得されたときには存在していなかったため、表示できません。 この場合、トランザクションはエラーを返します:ER_TABLE_DEF_CHANGED
、「テーブル定義が変更されました。トランザクションを再試行してください」。
読取りのタイプは、FOR UPDATE
または FOR SHARE
を指定しない INSERT INTO ... SELECT
、UPDATE ... (SELECT)
および CREATE TABLE ... SELECT
などの句での選択によって異なります:
デフォルトでは、
InnoDB
はこれらのステートメントに対してより強力なロックを使用し、SELECT
部分はREAD COMMITTED
のように動作します。この場合、各読取り一貫性は、同じトランザクション内であっても、独自の新しいスナップショットを設定および読み取ります。このような場合に非ロック読取りを実行するには、トランザクションの分離レベルを
READ UNCOMMITTED
またはREAD COMMITTED
に設定して、選択したテーブルから読み取られた行にロックを設定しないようにします。