ダーティリード、ノンリピータブルリード、ファントムリードを実演する

## はじめに
この記事ではデータベースの読み込み時に起こる3つの現象、ダーティリード、ノンリピータブルリード、ファントムリードについて実演することで理解を深めていこうと思います。

## この記事の対象者
- データベースを使った開発を行っている人
- データベーススペシャリストの資格を目指している人

## 実演環境
```
$ mysql --version
mysql  Ver 15.1 Distrib 10.5.4-MariaDB, for debian-linux-gnu (x86_64) using readline 5.2
```

実演用のテーブルおよびデータ

```
MariaDB [sample]> desc users;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | NO   | PRI | NULL    |       |
| name  | varchar(40) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+

MariaDB [sample]> select * from users;
+----+------+
| id | name |
+----+------+
|  1 | Adam |
+----+------+
```

## ダーティリード
### 説明
あるトランザクションがコミットされていない状態でも、別のトランザクションから変更内容を読み込めてしまう現象のことです。

### 実演
以下、ダーティリードを起こすために、トランザクションBのトランザクション分離レベルを`READ-UNCOMMITTED`に設定しています。

```
set session transaction isolation level read uncommitted;
```

```
Transaction B > select @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+
```

1. トランザクションAを開始する
![スクリーンショット 2021-06-06 12.16.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68663/3325d06f-5256-cc8a-6bac-264e8d5711b0.png)

2. トランザクションBを開始する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68663/8fbcce59-e399-7a4f-a50c-8896313cf8a1.png)

3. トランザクションAにてusersテーブルを更新する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68663/0c77603b-1bf5-3c28-e7d7-8f82324e2096.png)

4. トランザクションBにてusersテーブルを確認する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68663/1f8bd2d3-19c3-ba2b-5f44-f7932a5f25cc.png)

**まだ、トランザクションAはコミットしていませんが、更新したデータをトランザクションBから読み込めてしまっています。**

## ノンリピータブルリード
### 説明
あるトランザクションが2回同じデータを読み込む際に、1回目の読み込みと2回目の読み込みで値が異なってしまう現象のことです。

### 実演
以下、ノンリピータブルリードを起こすために、トランザクションBのトランザクション分離レベルを`READ-COMMITTED`に設定しています。

```
set session transaction isolation level read committed;
```

```
Transaction B > select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
```
1. トランザクションAを開始する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68663/f78f4b10-83eb-8dd5-dd18-0adcdbdd699f.png)

2. トランザクションBを開始する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68663/0ebd30a5-75f3-2560-7e24-e75c330888f6.png)

3. トランザクションBにてusersテーブルを確認する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68663/33045295-a463-6079-fe97-d740ce42d0ef.png)

4. トランザクションAにてusersテーブルを更新する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68663/e455bc29-0d7b-027a-c569-7da5db2ad31b.png)

5. トランザクションAをコミットする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68663/cd4588ff-6d24-6bf2-8315-002510dab6c9.png)

6. トランザクションBにて再度usersテーブルを確認する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68663/c76edb37-daec-a877-632f-5cff4c626389.png)

**トランザクションBにて1回目の読み込み(手順3)と2回目の読み込み(手順6)の内容が異なってしまっています。**

## ファントムリード
### 説明
あるトランザクションが2回同じデータを読み込む際に、2回目の読み込みで1回目には存在しなかったデータが読み込めてしまう現象のことです。

### 実演
以下、ファントムリードを起こすために、トランザクションBのトランザクション分離レベルを`READ-COMMITTED`に設定しています。
（詳しくは調べていませんが、MySQL(InnoDB)だと`REPEATABLE-READ`ではファントムリードが起きないようですので、`READ-COMMITTED`で確認します。）

```
set session transaction isolation level read committed;
```

```
Transaction B > select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
```

1. トランザクションAを開始する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68663/f78f4b10-83eb-8dd5-dd18-0adcdbdd699f.png)

2. トランザクションBを開始する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68663/0ebd30a5-75f3-2560-7e24-e75c330888f6.png)

3. トランザクションBにてusersテーブルを確認する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68663/33045295-a463-6079-fe97-d740ce42d0ef.png)

4. トランザクションAにてusersテーブルにレコードを追加する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68663/f4044c22-15f1-bcab-c64c-1db651c4611b.png)

5. トランザクションAをコミットする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68663/e2cd392e-7fa7-35a0-4694-1d307972d335.png)

6. トランザクションBにて再度usersテーブルを確認する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68663/749740af-2713-8ed7-0a07-fd2236635559.png)

**トランザクションBにて1回目の読み込み(手順3)には存在しなかったレコードが2回目の読み込み(手順6)で読み込めてしまっています。**

## トランザクション分離レベルごとでの現象確認
以上、3つの現象について実演してきたわけですが、これらの現象のどれが起きるかはトランザクション分離レベルによって決まります。
ただし、MySQL(InnoDB)の場合、`REPEATABLE-READ`でのファントムリードは起きないなど、データベースによって異なる場合もあるようですので、注意が必要です。

| 分離レベル | ダーティリード | ノンリピータブルリード | ファントムリード |
|:-----------|:------------:|:------------:|:------------:|
| READ-UNCOMMITTED | ○ | ○ | ○ |
| READ-COMMITTED   | - | ○ | ○ |
| REPEATABLE-READ  | - | - | ○ |
| SERIALIZABLE     | - | - | - |

## まとめ
この記事ではダーティリード、ノンリピータブルリード、ファントムリードを実演することで、それらの現象の理解を深めました。

私の経験上、業務の中でこれらの現象を意識して作業したということはほぼなかったと思いますが、データベースを使った開発を行う以上、トランザクション分離レベルによってはこういった現象が起きるということだけでも知っておくことは重要かと思います。

それではまた。

TomoProg