ロックあたりを調べていたら気になったので、ついでに調べました。
MySQL :: MySQL 5.6 リファレンスマニュアル :: 14.2.4 一貫性非ロック読み取り
ポイント
- ある時点でのデータベースのスナップショットをクエリーに提供
- トランザクション分離レベルが
REPEATABLE READ
の場合は、同じトランザクション内すべてのが一貫性読み取り - 一部のステートメントではデフォルトの設定において一貫性読み取りが提供されない
一貫性読み取り
一貫性読み取りとは、データベースのある時点での状態をスナップショットとして保持し、トランザクション内で一貫性のあるクエリを実現する事です。公式のドキュメントには以下のような記述があります。
一貫性読み取りとは、InnoDB がマルチバージョンを使用して、ある時点でのデータベースのスナップショットをクエリーに提供することを意味します。クエリーには、その時点よりも前にコミットされたトランザクションによる変更のみが表示され、その時点よりもあとのトランザクションまたはコミットされていないトランザクションによる変更は表示されません。
実際に試してみましょう。
REPEATABLE READで一貫性読み取りを試す
トランザクションの分離レベルを REPEATABLE READ
(デフォルト)にした状態で一貫性読み取りの動きを確認してみたいと思います。ちなみにトランザクション分離レベルは以下のQiitaの記事で調べたことをまとめています。
以下で使用する books
テーブルはこのようなテーブルです。
mysql> SHOW CREATE TABLE books;
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| books | CREATE TABLE `books` (
`id` int(11) NOT NULL,
`name` varchar(50) NOT NULL,
`publisher` varchar(50) NOT NULL,
`author` varchar(50) NOT NULL,
PRIMARY KEY (`id`),
KEY `books_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2つのトランザクションを開始し、一方で SELECT
を、もう一方で INSERT
を books
テーブルに行います。ステートメントの左側にトランザクション名を示しておきます。
T1> BEGIN;
T1> SELECT id FROM books;
+----+
| id |
+----+
| 3 |
| 1 |
| 2 |
+----+
T2> INSERT INTO books VALUES
(4,
'Effective Java (3rd Edition)',
'Addison-Wesley Professional',
'Joshua Bloch'
);
T1> SELECT id FROM books;
+----+
| id |
+----+
| 3 |
| 1 |
| 2 |
+----+
T1
トランザクションでのクエリ( SELECT
)がトランザクション開始時点から変化していないことがわかりました。これが一貫性読み取りと言われるものですね。ちなみに、一貫性読み取りはトランザクション分離レベルによって動きが変わります。
REPEATABLE READ
の場合には最初のクエリが実行されたタイミングでデータベースのスナップショットを取り、その後のクエリではそのスナップショットを使用したクエリ結果を返します。なので、上記の例にある INSERT
のタイミングを変えると、異なる結果になります。
T1> BEGIN;
T2> INSERT INTO books VALUES
(4,
'Effective Java (3rd Edition)',
'Addison-Wesley Professional',
'Joshua Bloch'
);
T1> SELECT id FROM books;
+----+
| id |
+----+
| 3 |
| 4 |
| 1 |
| 2 |
+----+
T1
トランザクションの最初のクエリ前に INSERT
が走ったので、 T1
トランザクションが使用するスナップショットは INSERT
の内容を反映したものになっています。
一貫性読み取りが効かないステートメントもある
一部、一貫性読み取りが効かない場合もあります。例えば、 INSERT INTO ... SELECT
構文で読み取られる行はデフォルトでは一貫性読み取りにはならず、他のトランザクションで変更された内容を読み取ってしまいます。そのため、以下のような状況がありえます。
T1> begin;
T1> select * from plans_stocks;
T2> begin;
T2> insert into plans_stocks values (\
-> 5,
-> 'Practical VIM',
-> 'Pragmatic Bookshelf',
-> 'Drew Neil');
T2> commit;
T1> insert into books \
-> select * from plans_stocks;
T1> select id from books;
+----+
| id |
+----+
| 3 |
| 4 |
| 5 |
| 1 |
| 2 |
+----+
上の例では T1
トランザクションで plans_stocks
テーブルの読み込みを行っており、その時のスナップショットをつかった一貫性読み取りが有効になっています。その後、 INSERT INTO ... SELECT
ステートメントを使って plans_stocks
から値を書き込んでいますが、読み込むデータはスナップショットからではなく、 T2
トランザクションからの影響を受けています。つまり、 INSERT INTO ... SELECT
構文での SELECT
には一貫性読み取りが有効になっていません。
このような場合で一貫性読み取りを有効にする方法もあるみたいです。(実際に試してはいませんが、このドキュメントの下部にその方法が書いてあります。MySQL :: MySQL 5.6 リファレンスマニュアル :: 14.2.4 一貫性非ロック読み取り)