分離レベルと取得されるロックの種類




[前に投稿した記事](https://qiita.com/chirotaro/private/b1d43f19aff2bf9fe1e0)では分離性、分離レベルについて記載しました。今回はこの続きを書こうと思います。

まず今回の投稿の内容の前提についてあらかじめ記載しておきます。
ここで書くロックの説明はDb2 LUWのロックのお話になります。
また分離レベルCSモードに関して、Currently Commited機能の使用有無によって同じCSモードでも挙動が異なってきますが、今回記載する内容はCurrently Commited機能はOFFでの動きを前提に記載しています。この点をご理解いただいた上で以下の内容を参考にしていただければと思います:sweat_smile:

#前回のおさらい
トランザクションの分離性は、ロック機能によって実装され、分離レベルの違いはオブジェクトに取得されるロックの種類・その組み合わせによって制御されています。

分離レベルはUR、CS、RS、RR４つの分離レベルがDb2では提供されており、URが一最も分離レベルが低く、他のトランザクションからの影響を最も受けやすく、RRが最も分離レベルが高く他のトランザクションからの影響は最も受けにくいということになります。

また同じ分離レベルであっても参照時と更新時ではオブジェクトに対して取得するロックの種類は異なります。


#Db2LUWで使われるロックの種類
分離レベルの違いとは、オブジェクトに対して取得するロックの種類・組み合わせの違いだと書きましたが、
ロックを取得するオブジェクトとその組み合わせについて詳しく説明します。

Db2 LUWではデータの排他制御のためにに**表と行に対してロックを取得**します。ここまでオブジェクトと曖昧に表していましたが、ここからはロックを取得する対象は、表または行という前提で進めます。（同じDb2ファミリーのDb2 for z/OSはまた違うのですがここでは割愛します。）

ロックの組み合わせというのは、この表に取得するロックと行に取得するロックの組み合わせのことを言います。


#ロックの種類の基本
ロックの基本はSロック,Uロック,Xロックの３種類です。

| ロックの種類    |   説明               |   
|:-------------:|:-----------------:|
| 共有ロック（S)  | 読み取り時に取得するロック          |      
| 更新ロック（U)  | 更新の**意図**があり、更新前に取得するロック|      
| 排他ロック（X)  |更新時に取得するロック| 

＜S（Share）共有ロック＞
読み取り時に取得するロックでSとUロックに互換性があります。

＜U （Update）更新ロック＞
更新前に取得するロックでSロックのみ互換性があります。

＜X（eXclusive）排他ロック＞
更新時に取得するロックで、どのロックにも互換性はありません。

基本的にS、U、Xの意味がわかっていれば、なんとなくこの後色々なロックの種類が出てきますが、大体のロックのイメージはつかめます。　　


#ロックの互換性
ロックの互換性とは他のトランザクションによって既にに表や行に取得されているロックがあった場合、同時にロックを取得することができるかということです。
互換性がある=同時に取得可能、互換性なし=同時に取得できないということになります。
互換性がないロックが既にロックを取得したいオブジェクトに取得されている場合、ロックは取得できないためそのトランザクションは既存ロックが解除されるのを待機します。

|               |       S            |    U               |  X       |
|:-------------:|:------------------:|:------------------:|:--------:|
| S             |          互換性あり |        互換性あり    |互換性なし 
| U             |          互換性あり |       互換性なし     |互換性なし 
| X             |          互換性なし |        互換性なし    |互換性なし 


詳しいそれぞれのロックタイプに対する互換性は[こちら](https://www.ibm.com/support/knowledgecenter/ja/SSEPGG_11.5.0/com.ibm.db2.luw.admin.perf.doc/doc/r0005274.html)をご参照ください。

基本的なロックの種類とロックの互換性の概念はこんな感じです。
ここからは分離レベルごとに、取得される表ロック・行ロックの組み合わせを整理していきます。


#更新時に取得されるロック
まず更新時に取得されるロックを考えます。更新時に取得されるロックはとてもシンプルです。

分離レベルでロックが異なると前置きしておいたのですが、
どの分離レベルでも**表に対してIXロックを取得し、更新行に対してはXロックを取得します**:sweat_smile:
行のXロックと表のIXロックについて説明します。

#####行へのXロック
Xロックは排他ロックなので、Xロックが行に取得されると、その行に対しては他トランザクションはどのタイプのロックも取得することができません。

#####表へのIXロック
IXロックのIはIntentを意味します。
このIntentというのは直訳すると意図。Intentロックは行にロックを持っているということを示すためのロックです。
つまり、IXロックとは、Xロックの意図、行に対してXロックを持っているよ〜ということを示すためのロックなのです。
なぜIntentロックが存在するのかというと、Intentロックがあることで、ロック取得のコストを減らすことができます。
例えば表全体にXロックを取得したいトランザクションがあったとします。そのトランザクションは他のトランザクションが行に対してSロックやXロックを取得していないか調べて、取得されていなければXロックを取得します。
その他のトランザクションが取得しているロックを調べるときに、表にIntentロック(ISやIXなど)を持っているトランザクションがないかどうかを確認するだけで、Xロックを取ってよいかどうか判断可能となり、ロック取得のコストを抑えることができるのです！



#参照時に取得されるロック
ここからが分離レベルによる違いが出てくる参照時に取得されるロックについて整理します。

####分離レベルURを指定した時に取得されるロック
URモードで参照を行うときは**表に対してINロックを取得し、行に対してはロックを取得しません**。

#####INロックとは？
IN（Intent None）ロックとはDb2LUW独自のロックの種類で、**URモードで参照する時のみ取得**されます。

URモードのときは他のトランザクションからの参照・更新・削除・挿入全てを許容するため本来ロックは必要ありません。
なので、表に取られるロックもIntent **NONE**（意図なし）ということで、行ロックを取得していませんよという意味になります。

じゃあ表ロックも不要なのではないかと思えてくるのですが、
ただ、流石に参照している時に表の削除（削除時はZロックが表に取得）が行われてしまうのはまずいので、最低限そういったことを防ぐためにURで参照しているトランザクションがいることを示す目的でINロックが取得されます。


####分離レベルCSを指定した時に取得されるロック
CSモードで参照を行うときは**表に対してISロックを取得し、行に対してはNSロックを取得**します。

####分離レベルRSを指定した時に取得されるロック
RSモードで参照を行うときは**表に対してISロックを取得し、行に対してはNSロックを取得**します。

####分離レベルRRを指定した時に取得されるロック
RRモードで参照を行うときは**表に対してSロックを取得し、行に対してはロックを取得しません**。

#####表へのSロック
[互換表](https://www.ibm.com/support/knowledgecenter/ja/SSEPGG_11.5.0/com.ibm.db2.luw.admin.perf.doc/doc/r0005274.html)によると、表へのSロックに互換性があるのはS(参照時）ロック、U（更新前の参照）IN（URの参照時）やIS（CS/RSの参照時）となっており、互換性のあるロックは全て参照のためのロックということがお分かりいただけるか思います。つまり更新時に取得されるロックはいずれも取得できないということになります。
RRは一度読み込んだデータは更新・追加させることは許容しませんので、後続トランザクションは参照するロックしか取得できないような制御をすることでRRモードを実現しています。

RRモードでの参照時に取得されるロック情報をdb2pdコマンドで確認してみました。
一番下の行にtype:table、mode:Sと出力されています。表(type)に対してSロック（mode）が取得されていることがわかります。

```ruby:RRモードでの参照時
db2 +c "select * from test with RR"

db2pd -db TESTDB -locks
Database Member 0 -- Database TESTDB -- Active -- Up 1 days 03:29:19 -- Date 2019-05-24-16.11.40.751717

Locks:
Address            TranHdl    Lockname                   Type           Mode Sts Owner      Dur HoldCount  Att        ReleaseFlg rrIID
0x00007F9F08EB3980 14         414141414148486993F89836C1 PlanLock       ..S  G   14         1   0          0x00000000 0x40000000 0
0x00007F9F08EB3380 14         020000000100000001008054D6 VarLock        ..S  G   14         1   0          0x00000000 0x40000000 0
0x00007F9F08EB3800 14         02001200000000000000000054 TableLock      ..S  G   14         1   0          0x00002000 0x00000001 0
```

#参照時のCSとRSモードでの違い
ちなみに先ほどRSの時に取得されたロックの種類について、私も最初は「ん？」と疑問を持ちました。
CSモードで取得されるロックと同じ種類・組み合わせてロックが取得されています。

RSは一度読み取ったデータは変更されてはいけない一方で、CSは一度読み取ったデータは変更しても問題ないというルールにて制御されるべきであるため、この２つのモードには大きな違いがあります。
なのに取得される取得されるロックの組み合わせはどうして同じなのか。
ちらべてみると、同じロックの種類・組み合わせでもロックの取り方に違いがあることがわかりました。。

RSモードで取得されたロックの情報を確認してみます。

```ruby:RSモード
db2 +c "select * from test where c2='test' with RS "
C1          C2
----------- -----
          1 test
          2 test
         11 test
         12 test
         13 test
         14 test
         15 test

  7 record(s) selected.



db2pd -db TESTDB -locks

Database Member 0 -- Database TESTDB -- Active -- Up 1 days 04:14:19 -- Date 2019-05-24-16.56.40.692373

Locks:
Address            TranHdl    Lockname                   Type           Mode Sts Owner      Dur HoldCount  Att        ReleaseFlg rrIID
0x00007F9F08EB5700 14         020012000E0000000000000052 RowLock        .NS  G   14         1   0          0x00000000 0x00000001 0
0x00007F9F08EB5A80 14         020012000F0000000000000052 RowLock        .NS  G   14         1   0          0x00000000 0x00000001 0
0x00007F9F08EB5980 14         02001200100000000000000052 RowLock        .NS  G   14         1   0          0x00000000 0x00000001 0
0x00007F9F08EB6080 14         02001200040000000000000052 RowLock        .NS  G   14         1   0          0x00000000 0x00000001 0
0x00007F9F08EB5F80 14         414141414148486993F89836C1 PlanLock       ..S  G   14         1   0          0x00000000 0x40000000 0
0x00007F9F08EB5580 14         02001200110000000000000052 RowLock        .NS  G   14         1   0          0x00000000 0x00000001 0
0x00007F9F08EB5C80 14         02001200050000000000000052 RowLock        .NS  G   14         1   0          0x00000000 0x00000001 0
0x00007F9F08EB5480 14         02001200120000000000000052 RowLock        .NS  G   14         1   0          0x00000000 0x00000001 0
0x00007F9F08EB6280 14         02001200000000000000000054 TableLock      .IS  G   14         1   0          0x00002000 0x00000001 0
```

```ruby:CSモード
db2pd -db TESTDB -locks		
		
Database Member 0 -- Database TESTDB -- Active -- Up 0 days 00:49:32 -- Date 2019-05-23-13.31.53.073699		
		
Locks:		
Address            TranHdl    Lockname                   Type           Mode Sts Owner      Dur HoldCount  Att        ReleaseFlg rrIID		
0x00007F9EFC9BF080 3          01000000010000000100604FD6 VarLock        ..S  G   0          1   0          0x00000000 0x40000000 0			
0x00007F9EFC9BFC00 3          02001200040000000000000052 RowLock        .NS  G   0          0   0          0x00000000 0x00000000 0		
0x00007F9EFC9BFC80 3          414141414148486993F89836C1 PlanLock       ..S  G   0          1   0          0x00000000 0x40000000 0		
0x00007F9EFC9BF400 3          02001200000000000000000054 TableLock      .IS  G   0          1   0          0x00002000 0x40000000 0
```

お分かりいただけたでしょうか、
RSでは参照結果として取得された行全てにNSロックを取得・保持していますが、CSでは今まさにカーソルがある位置のデータだけにNSロックが取得されます。
NS行ロックと、更新時に取得されるX行ロックは非互換です。
つまりCSでは一度取得したらロックは解除され、他のトランザクションが読み込んだ行を更新することを許容しますが、RSでは読み込んだ後もその行はそのトランザクションがcommitされるまでNSロックを取得したままなので、他のトランザクションからの更新を許容しません。



#参照時のRSとRRモードの違い
参照時のRSとRRモードでの制御の違いも、取得するロックの互換性を紐解くことで理解が深まりました。

RSは他のトランザクションからの行の挿入は許可します。一方でRRは行の挿入も許可しません。
つまりファントムリードが発生するかということが異なります。

RSでは既存の読み込んだ行だけにNSロックを取得します。新しく挿入される行に対しては取得していませんので、読み込みが完了した後に他のトランザクションからデータが挿入されて、X行ロックを取得することは許容されます。また表に取得されるロックもIXロック同士であり、互換性があるので問題ありません。
そのためRSではデータの挿入は許容されファントムリードが起こり得てしまうのです。

一方でRRでは上記の通りSロックを表に取得します。この場合新たに行が挿入しようとすると、IX表ロックを取得しようとしますが、既に取得されているS表ロックとは互換性がありません。そのためRRモードのときは新たな行の挿入もできず、新規追加行の読み取り、ファントムリードが防げるということになります。



#まとめ
各分離モードで取得されるロックをまとめると以下のようになります。
なかなか、この分離レベルの時はどんなロックが取られるのか覚えておくのは大変ですが、一度改めて整理してみると、すっきり:sunny:しました！
最後まで読んでいただきありがとうございました！

|               |     参照(表/行)    |    更新(表/行)    | 
|:-------------:|:-----------------:|:----------------:|
| UR            |         IN/なし    |       IX/X      |
| CS            |         IS/NS     |       IX/X       |
| RS            |         IS/NS     |       IX/X       |
| RR            |         S/なし     |       IX/X       |