🔖

データベース/Anomalyについて

に公開

Anomaly

昔はDirty Read,Non-Repeatable Read,Phantom Readしか気にしてなかったけどそんな時代じゃない。改めてまとめてみた。
正直DBの実装にひっぱられていて一般化するのが難しい気はしている。

以下サイトが元ネタ。
自分なりにかみ砕いてみた。
https://blog.acolyer.org/2016/02/24/a-critique-of-ansi-sql-isolation-levels/

https://qiita.com/kumagi/items/5ef5e404546736ebac49

https://zenn.dev/mpyw/articles/rdb-transaction-isolations

https://tombo2.hatenablog.com/entry/2017/10/17/010057

https://qiita.com/you06/items/8345670505cd2065732a

書き込み不整合

P0:Dirty Write

あるトランザクションが、別のトランザクションの一部として書き込まれたコミットされていない値を上書きすると発生する。

Dirty Writeが良くない理由の1つは、データベースの一貫性を損なう可能性があることです。xとyの間に制約があり (x=yなど)、T1とT2はそれぞれ、単独で実行した場合に制約の一貫性を維持するとします。ただし、2つのトランザクションがxとyを異なる順序で書き込むと、制約に簡単に違反する可能性があります。これは、Dirty Writeがある場合にのみ発生する可能性があります。

Dirty Writeから保護する必要があるもう1つの差し迫った理由は、Dirty Write保護しないと、システムがトランザクションアボート時にビフォアイメージに自動的にロールバックできないことです。

x=yという制約があるときに次の順でTx1がx=y=1と書き込み、Tx2がx=y=2と書き込むとx=yではなくなって不整合が発生してしまう。


読み取り不整合

P1:Dirty Read

別のトランザクションにより書き込まれたコミットされていない値を読んでしまう。

  • 最終的にロールバックされる更新
  • 最終的にコミットされる更新
    のどちらも読み取りを防止する必要がある。

xとyのバランス間の移動を考える。
x+y=100という制約があるとき以下が不正になる。

P2:Non-Repeatable Read/Fuzzy Read

進行中のトランザクションによって読み取られた値が別のトランザクションによって上書きされた時に発生する。値の2回目の読み取りが実際に行われなくても、データベースの不変条件に違反する可能性がある。

値の2回目の読み取りが行われた例

値の2回目の読み取りが行われない例

x+y=100という制約があるとき以下が不正になる。

P3:Phantom

トランザクションがSELECT… FROM… WHERE… 実行し、最初のトランザクションの処理中に、別のトランザクションが条件に一致する項目を書き込むと発生する。ANSIでは、一致する挿入だけ禁止されていたが、実際には、更新、削除、挿入の書き込みを禁止する必要がある。

単純な例

元サイトの例

A5A:Read Skew/Inconsistent Read

複数の値の間で整合性の取れていない値を読み込んでしまう。
Tbl1とTbl2を読むときに「Tbl1がこの値ならTbl2はこの範囲の値である」という前提が崩れる可能性がある。
x+y=100という制約があるとき以下が不正になる。


更新競合

P4:Lost Update

Tran1データを読み取り、Tran2が同データを更新、その後Tran1が読み取ったデータをもとに更新するとTran2の更新が失われる。
READ COMMITTED(Dirty Write、Dirty Readを防ぐ)で発生する。
REPEATABLE READ(Fuzzy Readを防ぐ)なら発生しない。

P4C:Cursor Lost Update

あちこちでAnomalyの一つみたいに書かれているので混乱する。
実際にはLost Updateと同じだけど防ぎ方の違いを言っているもののように思える。
Lost UpdateはREPEATABLE READで防げるんだけど、RRじゃなくてLOCKING READすることでも防げるよね?ということを言いたいみたい。
以下はLost Updateと同じことをLOCKING READした場合。
Postgresqlでfor updateで読むならTran2のread cursorのところでTran1のCommitかrollbackを待たされるので発生しない。


直列化異常

A5B:Write Skew

  • Tx1がxを読み取りyを更新
  • Tx2がyを読み取りxを更新
    とするときにお互いが更新前の値に基づいた更新をしてしまう。
    x+y≤100という制約があるときに以下の順序で動くと不整合が発生する。

こっちの例の方がいいかも
https://qiita.com/kumagi/items/5ef5e404546736ebac49#write-skew-anomaly
Tx1:y=x+1
Tx2:x=y+1
としようとしており、初期状態は
x=0
y=0
とする

Serializableであればこうなる

Read Only/Observe Skew

同じくここから
https://qiita.com/kumagi/items/5ef5e404546736ebac49#write-skew-anomaly
観測者がいなければSerializableに実行されたとみなせる処理が、観測者としてReadOnlyなトランザクションがいるとSerializableだとみなせなくなる。
初期状態は
x=0
y=0
とする
Tx1: xに5を足す
Tx2: x == yの時にyから10を引く
Tx3: xとyの値を読んでユーザに報告する
ここではTx3が観測者になる。

fig1

正直、初見では理解できなかった。
まず観測者のいない世界を考えてみる。

fig2

Tx1がxを5にupdateした後にスナップショットをみたTx2がX==yだと思って-10してしまっているがこれは以下の順で処理が起こったのだ、と解釈すれば矛盾がない。

fig3

現実には
Tx1:commit->Tx2:commit
の順で動いているのだが、
Tx2:commit->Tx1:commit
だったことにすればSerializableに実行されたとみなせる。

観測者がいなければそれでいいのだが、fig1ではTx3が
x:5
y:0
をユーザに報告している。
が、fig3だと解釈した世界線にはx:5、y:0だった瞬間はない。
ありもしない(実際にはあったけど)x:5、y:0を報告してしまう、という不整合が発生している。

Discussion