【9日目】トランザクションサポートデータベース実装

# まえがき
本日はRDBマジックの一つ、可視性の制御について記載します。
※RDBマジックとは、「あっ、そういう仕組みで実現してるんだ！」と驚いたRDBMSの持つ仕掛けのことを指しています。多分あと3個ぐらい出てくると思います。
というわけで、下記の二つの可視性について考えてみます。

* 自トランザクションの変更は、トランザクションをコミットするまで、他トランザクションからは参照できない
* 自トランザクションの変更は、自トランザクションから常に参照できる

## 可視性制御(Visibility Control)の概念
RDBMSの可視性制御とは、実データとそれにアクセスするクライアントとの間にフィルターとして存在し、クライアントに対して更新途中のデータを参照できないようにしたり、削除されたデータをまだ存在しているかのように見せたりする仕組みのことです。実データファイルには物理的にレコードが存在していたとしても、クライアントから見るとレコードが無くなったり、その逆に増えたりするのです。更新前の古いレコードが見えたりもします。クライアントに返却するデータを意図的に、データベースの都合が良いようにねじ曲げるのです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/bc77301a-f382-5ae7-1c52-923ce4928440.png)

可視性制御は、自トランザクションが実行した挿入・変更・削除操作が、コミットされるまでは他トランザクションから参照できないようにする仕組みを実現します。同時に、自トランザクション内でレコードを挿入、更新した場合、自トランザクションからはもちろんそれらのレコードが参照できなければなりません。それもまた、可視性制御によって実現します。

## 可視性制御の実装方針
### 自トランザクションの変更は、トランザクションをコミットするまで他トランザクションから参照できないようにする
実装の考え方としてはgitと似ていると思います。データの挿入・更新・削除操作は自トランザクション用に用意されたローカルデータ領域で行い、コミット操作によってそれらの変更を共有のデータ領域に書き込むようにします。そうすることによって、自トランザクションでの変更はコミットされるまで、他トランザクションからは参照できません。

1. トランザクション開始時にローカルデータ領域を用意する
2. ローカルデータ領域に対して、変更操作を実行する
3. トランザクションがコミットされたら、共有データ領域に変更内容を書き込む

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/433b6063-71e5-8298-9dbc-b0d8b58d374c.png)

このようにすると、ロールバックの操作が簡単に実現できます。すなわち、トランザクションのロールバックが発生した場合は、単純にそのトランザクションのローカルデータ領域を削除します。
この実装手法のデメリットは、コミット操作が重くなります。コミット操作によって、ローカルデータ領域の全更新データを共有データ領域へ書き込むためです。

ローカル領域に書き込むデータは、トランザクション内で発生した更新データのみとします。そのため、データベースの操作は常に、共有領域とローカル領域をうまくマージして処理する必要があります。
gitでマスターブランチをチェックアウトしてきた時のように、共有領域の持っているデータを全てローカルに持って来る方法もありますが、ローカル領域が肥大してしまうため採用しません。

### 自トランザクションの変更は常に自トランザクションから参照できる
当たり前といえば当たり前ですが、トランザクション内でテーブルにデータを1行INSERTすれば、コミットする前であっても、そのテーブルをSELECTしたとき1行増えてなければいけません。データベースに対して自分が行う操作は、その都度、あたかも直接データベースを操作しているように見せなければいけないのです。
今回実装してきたシンプルデータベースは、カラムインデックスがデータのオブジェクトIDを指し示し、そのオブジェクトIDをキーにデータを検索するという構成になっていました。この構成に合わせて、可視性の制御を実装するなら下記のようになります。
表中の左側にこのトランザクションのローカルデータ領域、右側に共有データ領域を示します。

#### INSERT
フルーツテーブルに[バナナ,100円]を追加する

キーバリューストアはローカル領域でレコード追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/868838e3-3d63-1edf-301a-dccbc1eff49c.png)

カラムインデックスも各カラムごと、ローカル領域でインデックス追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/b353fd05-edd5-9feb-ec15-29f4b8a065eb.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/fcfd1871-f462-bac8-36a2-79def1cae7e2.png)

自トランザクションに対する可視性制御は、これらのローカルデータ領域と共有データ領域の持っているレコードをマージする形になる。
（キーバリューストアのマージは単純にローカルデータ領域と共有データ領域のレコードたちを和集合するだけだが、カラムインデックスの場合、キーがすでに存在する場合があるので注意。上で言うと共有データのカラムインデックス（価格）は{100,[A,D]}というレコードにマージされる。）

#### UPDATE
りんごの値段を100円から120円へ値上げする

キーバリューストアはローカル領域にレコード追加。自トランザクションからの参照時は、ローカル領域のAと共有領域のBとCをマージする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/1fba605d-11a7-c4f3-3ed5-39a1dcbb5a14.png)

カラムインデックスは更新カラムのみレコード追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/d432ab3e-ad66-86a6-d3c9-c7fe602c1dfa.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/06ffd6a5-fcd3-8c63-d4a1-ff49f2d9bb59.png)
自トランザクションに対する可視性制御は少しやっかいである。カラムインデックス（価格）のあるべきマージ後の姿は下記となるだろう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/13a571cc-0bfe-8cd4-2234-4a203f25a1e8.png)

#### DELETE
りんごのレコードを削除することを考える。削除の場合、キーバリューストアもカラムインデックスもローカル領域への書き込みは必要ないが、自トランザクションに対する可視性は、削除されたレコードをクライアントに見せないよう制御する必要がある。したがって、カラムインデックスは下記のように見えるべきである。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/15fad41d-9bbd-7807-cfd4-3d76c34becd5.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/8104e102-4770-8706-6b86-b09aa31d4262.png)

# まとめ
可視性制御というのはRDBの本を読んでいてもあまり出てこない言葉です。物理的なレコードの状態をクライアントから直接見えないようにフィルタリングする大事な役割を担っています。自トランザクションに対してどう見せるか、他トランザクションからどう見られるか。だんだん複雑になってきましたね。。
さて、概念と実装方針はだんだん固まってきましたので、次は複数回の変更操作を行った場合の挙動について考えていきます。