[DB] トランザクションと隔離性

# はじめに

DBは、トランザクション単位で処理されるそうです。 有名なAll Or NothingがDBのトランザクションから出た話です。 今日は、皆さんにDBを利用する際のトランザクションと隔離性(Isolation)について調べる時間を持ちたいと思います。

## トランザクション(Transaction)

作っているプロジェクトのロジックの中に、DBテーブルに情報をinsertするロジックがあります。 テーブルは正規化されていて、A、B、Cテーブルの順番にテーブルをinsertするとします。 テーブルが割れているので、テーブル間のデータ整合性を保つことが重要です。 もし、A、Bにはinsertして、Cにinsertする前にサーバがエラーまたは特定の作業によって正常に処理できない状況になったとします。 そうすると、結果的にこのデータは信じられないデータとなります。 これを解決するための方法として、DBはAll Or Nothing戦略を行います。 つまり、A、B、Cに正常にinsertしたり、どこにでもinsertしないことです。 DBでは、この戦略を取るためにトランザクションという単位を使用しています。 トランザクション単位は、分割されない最小限の単位と定義します。 ロジックにトランザクションが適用されると、エラーが生じた場合はA、BにinsertしたものをRollbackすることによってNothingで戦略をとります。

トランザクションは、4つの特徴を持っています。 これを頭文字だけとってACIDと呼びます。

・原子性(Atomicity)
トランザクションは、もはや分解不可能な業務の最小単位であるため、すべて処理されるか、あるいは、一から一つも処理されてはならない。

・一貫性(Consistency)
一貫した状態のデータベースで1つのトランザクションを成功裏に完了すると、そのデータベースは依然として一貫した状態でなければならない。 すなわち、トランザクションの実行の結果として、データベースの状態が矛盾してはならない。

・隔離性(Isolation)
実行中のトランザクションの中間結果に他のトランザクションがアクセスできない。

・永続性(Durability)
トランザクションが一旦その実行を成功裏に完了すると、その結果はデータベースに永続的に保存される。

## トランザクション隔離性(Transaction Isolation)

トランザクションの隔離性について、もう少し深く見てみましょう。 隔離性は上記の特徴から見てきたように、実行中のトランザクションの中間結果を他のトランザクションがアクセスできない。という定義を持っています。 漠然としてアクセスできないというよりは、一般的にアクセスレベルがあり、DBによって設定が可能です。 このような隔離性は強く処理することができるし、逆に弱く処理することもできます。 下からもう少し見てみましょう。

## 隔離性によって起こりうる問題点

隔離性によって現れる問題点は、一般的にDirty Read、Non-Repeatable Read、Phantom Readの3つだと言います。

## Dirty Read 

Dirty Read は、他のトランザクションによって修正されたが、まだコミットされていないデータを読み取ることです。 以下の画像をご覧いただくとご理解いただけると思います。 このような場合、Transaction_1が正常に処理されずにRollbackされることがあります。 この場合、その値をすでに読んだTransaction_2は誤った値で本人のロジックを処理する状態に置かれることになります。

![Dirty Read.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619610/4e4f66a0-50ea-4c71-91d3-23289215152a.png)

## Non-Repeatable Read

Non-Repeatable Readは、トランザクション内で同じキーを持つRowを2回読み込み、その間に値が変更されたり削除されたりすることによって、結果が異なるように表示される現象を指します。

![Non-Repeatable Read.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619610/774206e6-168b-6440-ba83-cc4781943def.png)

## Phantom Read

あるトランザクション内で同じクエリーを 2 回実行したが、最初のクエリーになかった幽霊（Phantom）レコードが、2 番目のクエリーで表示される現象のことです。
ここで、Phantom ReadとNon-Repeatuable Readに分けられます。 Non-RepeatableReadは1つのRowのデータの値が変更されるもので、PhantonReadは多件要請に対してデータの値が変更されるものです。

![Phantom Read.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619610/5b12c702-7d9a-1a7a-f288-08a7046bcef5.png)

## 指定できる隔離レベル

それでは、次にDataBaseが提供する隔離性レベル(Transaction Isolation Level)について見てみましょう。 以下の 4 つの隔離レベルは、ANSIISO SQL 標準（SQL 92）で定義された内容です。

## Read Uncommitted

トランザクションで処理中のまだコミットされていないデータを他のトランザクションが読み込むことを許可します。 そのレベルではDirtyRead、Non-RepeatableRead、PhantomReadが起こることがあります。 この設定は整合性に問題があるため、推奨する設定ではありません。

## Read Committed

トランザクションがコミットされ、確定されたデータだけを他のトランザクションが読み込むように許可します。 したがって、Dirty Readの発生可能性を防ぎます。 コミットされないデータは、実際のDBデータではなく、Undoログにある以前のデータを取得するものです。 しかし、Non-Repeatable ReadとPhanton Readに対しては発生する可能性があります。

## Repeatable Read

トランザクション内で削除、変更に対して Undo ログに保存し、前に発生したトランザクションについては、実際のデータではなく、Undo ログにあるバックアップ データを読み込ませます。 こうすることで、トランザクション中の値の変更に対して一定の値で処理することができます。 こうすると、削除と修正に対してトランザクション内の不一致を取得したNon-Reapeatable Readを解消することができます。 Multiversion Concurrency Control

## Serializable Read

トランザクション内でクエリーを 2 回以上実行するときに、最初のクエリーにあったレコードが消えたり、値が変わらなかったり、新しいレコードが表示されないようにする設定です。