Read-Onlyのトランザクションがデッドロックした


デッドロックによりアボートされたジョブがあるので原因を調べてくれ、と言われたのでヘラヘラしながら調べ始めたら、原因がさっぱり分からなくて参った。
デッドロックしているのだから

という格好になっていて、更新の順番をa->bに揃えてやれば解決すると思ったのだが、アボートされたトランザクションはどうみても参照のみで、更新なんかしていないなのだ。
readしかしてないのにデッドロック...read...ロック...readロック... と考えていたら、脳天から焼け火箸を叩き込まれたような衝撃とともに原因がわかった。

ロック機構による同時実行制御

RDBの同時並行制御機構には、MVCCによる実装とロックだけの実装があって、前者は同時並行性に優れる、ということになっている。
MVCCではないRDBというのはもうあんまり残ってないと思うが(DB2がそうらしい)、ウチの会社が主戦場にしているTeradataはその残党の一つなのだ。
ロック機構だけで同時並行制御を実装したRDBでは、readアクセスがwriteアクセスをブロックする。つまり、あるトランザクションがあるテーブルを読んだら、そのトランザクションがcommit/rollbackされるまで、そのテーブルへのwriteアクセスは待たされる。だから、ロック機構はMVCCに比べて同時並行性に劣るのだ。
ここまではいろんな本に書いてあることで、自分は分かったような気になっていたのだが、「ロック機構による同時実行制御では、read-onlyのトランザクションデッドロックする」という恐ろしい帰結に気付いていなかったのだから、実質的に何も分かっていなかったのだ。


何が起きたのか整理すると、Teradataでは「readアクセスがwriteアクセスをブロックする」のだから、

というトランザクション間にもデッドロックが発生する。つまり、参照のみのトランザクションデッドロックの原因になるのだ。


ということは、ロック機構だけで同時並行制御を実装したRDBで、OLTPなアプリケーションを作る時は、テーブルの更新順序だけでなく、参照順序まで整列しなくてはならないのだ。
これは結構大変なことじゃないか。めんどくさいていうか。昔の人はどうしてたんだ。



追記

koichikさんにコメントいただいて分かったんだけど、この障害はトランザクション分離レベルが serializable になっていることがポイントだった。
Teradataがサポートする分離レベルは、ANSI標準でいう read uncommitted と serializable しかないんだけど、他の非MVCCのRDBではふつうに read committed がサポートされているし、多分デフォルトの分離レベルになっているだろう。
なので「read-onlyトランザクションデッドロック」というのは非MVCCのRDB一般の注意事項じゃなかった。
ていうかkoichikさんてあのid:koichikさんじゃないの。「Hibernate入門記」には大変お世話になりました。