楽観的ロック

Rockではなくて,鍵の方,Lockのお話.データベースのトランザクションに関する排他制御で使用されるロックのお話である.データベースが内部で用いるロック*1ではなく,ユーザーセッションがデータを扱う上で使用するロックの話なので注意して欲しい.
このロックではたいてい3種類のロックのパターンがある

Pessimistic Lock(悲観的ロック)

セッションが情報を取得するときに情報を取得した全タプルについてロックを得てしまう方法である.これは,狭義のロックといえるもので,技術者同士で話をしているときは,どちらの意味でロックのことをいっているかどうかを注意をしておく必要がある.
なぜ,この方式が悲観的かというと,「あらかじめデータに鍵をかけておかないと,他のセッションにデータを変更されるから.」であり,さらに,「読み込んだ全体のデータについてどのデータが変更されるかわからないから,すべてのデータに鍵をかけておかなければならない」という,マイナス思考の(笑)予測に従っているからである.
実装は非常に簡単であり,安全確実である.というのも,selectの後ろに(OraclepostgreSQLの場合は)for update nowaitをつけておけばよいだけだからである.nowaitを付けておけば,ロックが取得できなかった場合はすぐに制御が戻るため「他のユーザーが使用中です」とメッセージを出して,再検索を要求すればよい.
デメリットは早い時点でロックを取得するため,ユーザーが検索後になにも処理をせず放置されてしまうと,いつまでたっても他のユーザーがアクセスできないという点である.個人個人が更新できる情報がそれぞれ異なる場合は,それでもかまわないのだが,例えばビジネスでよく使われるような「製品の一覧」など他のユーザーも使用する共通した情報に対して更新ができるような場合は,問題が発生する.したがって,以下のようなことに注意をする必要がある.

  1. あまり大きな範囲でロックをとらない
  2. 一定時間に操作がない場合はセッションのタイムアウトを起こして,セッションを切断し,ロックを解放する(DBMSにタイムアウトの時間を適切に設定をすればよい)
  3. SQL ServerDB2など,内部の排他制御のしくみがMVCC(マルチバージョン型同時実行制御)方式でない場合は,更新時だけでなく,selectにfor updateを付けない場合でもデータベース内部に共有ロックをかけてしまうため,該当のデータを含む結果セットを生成する際も待ちに入ってしまい,更新に関係しない検索にも影響を及ぼしてしまう
  4. 検索結果を破棄するときにロックを破棄することを忘れないようにしなければならない

Optimistic Lock(楽観的ロック)

これは,逆に登録直前までロックを行わない方式である.これは別名タイムスタンプ方式(時刻印方式)ともよばれ,ロックとは別であると考える場合もある.
これがなぜ楽観的なのかといえば,「同じ状態のデータについて.異なるユーザーセッションが同じタプルのデータを更新することは,滅多にない」と考えるためである.とはいえ,一貫性を保つ必要があるので,登録直前に検索によって取得したデータが他のユーザーションによって更新されていないかどうかを見極め,更新されていなければ,登録を行う.
この場合は,検索時は普通の検索を,登録直前にfor updateを付けた検索を行い,ロックを取得すると同時に二つの検索した結果に差があるか調べ,変更が行われているかどうかをチェックする.変更が行われてしまった場合は,更新をすべて破棄し,もう一度ユーザーに入力を求めなければならない.
この場合は,ロックの時間が極めて短いため,悲観的ロックでのデメリットを解消している.ただ,大きな問題として,ロックの取得に失敗した場合はユーザーに再度入力を求める必要があり,せっかく入力した内容がすべてご破算になるという問題がある.また,タイムスタンプの比較はSQL一発でどうにかなるというものではなく,アプリケーション側で何とかしないといけない.ここにバグが潜んでしまうとタイミングによってデータがおかしくなるという恐ろしいシステムができあがるため,テストも慎重に行う必要がある.

Cautious Lock

私はコーシャスロック(慎重なロック)と呼んでいるが*2,余り一般的な言い方ではないらしい.googleでもヒットしなかった.このロックは,検索時ではなく,ユーザーによってテキストボックスに値が書き込まれるなど,編集が行われた瞬間にロックをとるというものである.編集をするということはそのタイミングで変更する意志があるとみなし,該当のデータにのみロックを取得する.このとき,for updateでロックを取得するのと同時に,再検索したデータと比較を行う.すでに変更が行われていた場合は,「すでに他のユーザーによって変更されているため,再検索します」と表示し,画面をreloadしてからユーザーによる編集を反映させるかどうかを判断する.それ以降は悲観的ロックと同様に扱う.
楽観的ロックと悲観的ロックの両方を使用した方式で,さらにアプリケーション側の負担は大きい.ただし,「更新を破棄する」という操作が必要ではないので,最もユーザーにとっては便利かもしれない.
問題は,画面を編集するというタイミングと反映をアプリケーション側で判断できるかという点である.ウェブベースのアプリケーションなどはAJAX等を使わないと難しいかもしれない.また,バグによるデータ破壊のリスクもある.

で,結局どちらがいいのか

といわれると,まぁ時と場合によるとしかいいようがない.Optimistic Offline LockはOfflineというからユーザーセッションが維持されていない状況でも機能するようにしているのだと思う.OFFLINEにしてしまうとDBMSの機能は使えないから,確かにアプリケーション側でがんばるしかない*3.ただ,ウェブページをreloadするたびにセッションを作り直すようなしくみにしている場合は,この方式をとらざるを得ないが,普通はアプリケーションサーバー側から送り込まれたクッキーでセッションを判別して処理をするため,別に問題がないような気もする.Optimistic Offline Lockの説明にはこうある

Often a business transaction executes across a series of system transactions. Once outside the confines of a single system transaction, we can't depend on our database manager alone to ensure that the business transaction will leave the record data in a consistent state.

英語力がないので,申し訳ないのだが,ようするに(たぶん)「ビジネストランザクション複数の一連のシステムトランザクションを経て実行されることがしばしばみられる.単一のシステムトランザクションならともかかく,それが複数になるとDBMSに一貫性の維持を任せることはできない」ということではないだろうか?
問題はビジネストランザクションがどうして複数の一連のシステムトランザクションになってしまうのか?ということ.通常,一つのトランザクションには複数SQLが含まれるはずであり,すべてのが処理された後にcommitまたはrollbackがおこなわれ,DBMSが適切に判断してくれる.しかし,この記述だけを読むと,一つのSQLが一つのトランザクションのような印象を受けてしまう.ドメインモデルによるビジネスロジックの実装においてトランザクションの開始と終了が,データベースのアクセス層に伝達できない,という事なのだろうか?つまり,システムトランザクションとはあくまでもデータベースのアクセス層のメソッド内におけるトランザクションレベルであり,それらを複数複数使用するのがドメインモデルにおけるビジネストランザクションなのだろうか?
勝手な想像だけで話を進めてもしかたがないので,まずはこの辺でやめておくことにしたい.

*1:代表的なものに共有ロックと排他ロックがある

*2:使っていた4GLがそういう言い方をしていた.ようするに悲観的,楽観的よりはより慎重だということだと思う.

*3:この場合は,悲観的ロックの実装もかなり面倒なことになる