排他って何だろう


福岡寿和 FUKUOKA,Toshikazu
富士通SSL


はじめに

 同時にひとつの資源を複数のプログラムから使おうとしたときは,必ず「排他」というものが必要です.今回は,いろいろある排他問題の中から,RDBMSの世界での「排他」,特にレコード変更(挿入・更新・削除)に関係するものに注目してみます.なお,今回のリストプログラムは,Oracle Objects for OLEをミドルウェアとして,Oracle7またはOracle8をターゲットにしています.また,事前にリスト1を実行して,テスト用のテーブルを作成してください.

リスト1:サンプル用定義スクリプト
DROP TABLE VBMArea CASCADE CONSTRAINTS;

CREATE TABLE VBMArea (
   AreaCode       CHAR(2)      NOT NULL,
   AreaName       VARCHAR2(10) NULL,
   UpdateTime     DATE         NOT NULL
);

CREATE OR REPLACE TRIGGER VBMArea_BIU BEFORE INSERT OR UPDATE ON VBMArea
REFERENCING OLD AS OLD NEW AS NEW FOR EACH ROW
DECLARE
BEGIN
   :new.UpdateTime := sysdate;
END;
/

ALTER TABLE VBMArea
       ADD  ( CONSTRAINT XPK_VBMArea PRIMARY KEY (AreaCode)
       USING INDEX);

ふたつの排他

楽観的排他(オプティミスティックロック)

 楽観的ロックは,
自分が操作している情報は,他の人が操作する可能性が少ない.
という視点に立った排他制御方法です.楽観的ロックの利点は,画面に情報を表示しただけでは,他の利用者に影響を与えないという点です.このため,顧客管理など管理単位が個々に独立した業務での同時実行性を向上させ,ロック待ちのイライラを解消します(図1・リスト1).
 ただし,画面上に表示している情報が,最新のものかどうかの保証はありません.他の利用者からの更新が自動的に通知されることはなく,更新操作に対するエラー情報として,初めて認識することができます(図2).そして,このようなエラーが発生したときに,画面上で更新した情報をRDBMSに反映するには,再度レコードを読み込む(もちろん,画面には反映しないで)必要があります.

図1:楽観的ロック
図1:楽観的ロック

図2:エラーで知る更新状況
図2:エラーで知る更新状況

悲観的排他(ペシミスティックロック)

 悲観的ロックは,
自分が操作している情報は,他の人も操作する可能性がある.
という視点に立った排他制御方法です(図3・リスト2).悲観的ロックの特徴は,画面に表示した情報は,自分が変更するまで保証されている点です.この特徴が利点となるのは,在庫確認をして,在庫があれば,出庫処理をするような在庫管理の業務です.なぜなら,このような在庫管理のシステムでは,確認している最中に在庫数が変化したら,システムが立ち行かなくなってしまいます.つまり,悲観的排他により「同時に倉庫に入れる人はひとり」と取り決めるようなものです.
 リストでは,他の利用者が情報を参照していると同一情報の参照が待たされます.もし,他の利用者の参照終了を待つのではなく,参照していることだけを知りたいときは,発行するSQL文をリスト2のように「NOWAIT」を付加することにより,リソースビジーとして即時復帰することができます(図4).

図3:悲観的ロック
図3:悲観的ロック

リスト2:NOWAITオプション
strSQL = "SELECT * FROM VBMArea WHERE AreaCode='" & _
          txtAreaCode & "' FOR UPDATE NOWAIT"

図4:エラーで知る排他状況
図4:エラーで知る排他状況

悲観的排他は,魔法の処方箋ではない

 悲観的排他は,「自分が更新しようとしている情報を他の人に更新させない」ことから,概念的な美しさを伴っています.しかし,悲観的排他を採用したときは,画面を開いたまま,離席しないよう運用でカバーしたり,または,プログラム的には,画面を表示してから,一定時間キー入力がなければ,自動的に表示した情報を破棄するように作り込む必要があるでしょう.また,ごく普通のシステムでは,このような厳密な排他制御方式を必要とはしません.

実用的な排他

 楽観的排他をもう少し改良するとプログラムがすっきりします.たとえば,画面を表示するときから更新するときまで,同一のダイナセットを使うのではなく,表示時と更新時にそれぞれダイナセットを生成するのはどうでしょうか(図5・リスト3).
 こうすることにより,画面表示中に他の人が情報を更新したとしても,その更新結果に左右されずに動作させることが可能です.リストでは,Oracleのトリガー機能を使って,テーブル上に更新日時を設定し,それを画面表示時と情報更新時に比較することにより,他の利用者からの変更を認識して,注意を促すようにしています(図6)が,もちろん,時間的に最後に更新した内容が正しいとすれば,この部分のロジックは必要ありません.

図5:表示時・更新時にダイナセットを生成する
図5:表示時・更新時にダイナセットを生成する

図6:Oracleのトリガー機能を使った例
図6:Oracleのトリガー機能を使った例

リスト3:表示時と更新時にダイナセットを生成する
Private Sub cmdUpdate_Click()
  Dim strErrText      As String
  Dim objDs           As Object
  Dim strSQL          As String
  Dim strUpdate       As String

  On Error GoTo errClick:

  MousePointer = vbHourglass
  strSQL = "SELECT * FROM VBMArea WHERE AreaCode='" & txtAreaCode & "' FOR UPDATE"
  Set objDs = pobjDb.DBCreateDynaset(strSQL, 0&)
  If objDs.EOF Then
      objDs.DbAddNew
  Else
    strUpdate = Format$(objDs("UpdateTime").Value, "YYMMDDHHNNSS")
    If mstrUpdate <> strUpdate Then
      If MsgBox("他のユーザにより更新されています(" & strUpdate & ")." & _
       vbCrLf & "更新します", _
              vbOKCancel + vbExclamation, App.Title) = vbCancel Then
        GoTo exitClick:
      End If
    End If
    objDs.DbEdit
  End If
  objDs("AreaCode").Value = Trim$(txtAreaCode)
  objDs("AreaName").Value = Trim$(txtArea)
  objDs.DbUpdate

  cmdSearch.Enabled = True
  cmdUpdate.Enabled = False

exitClick:
  On Error Resume Next
  Set objDs = Nothing
  MousePointer = vbDefault
  Exit Sub

errClick:
  If pobjSess.LastServerErr = 0 Then
    If pobjDb.LastServerErr = 0 Then
      If Err <> 0 Then
        strErrText = Error$
      End If
    Else
      strErrText = pobjDb.LastServerErrText
      pobjDb.LastServerErrReset
    End If
  Else
    strErrText = pobjSess.LastServerErrText
    pobjSess.LastServerErrReset
  End If
  MsgBox strErrText, vbOKOnly + vbExclamation, App.Title
  Resume exitClick:
End Sub

行排他とページ排他

 RDBMSの排他単位にもいろいろありますが,現在では,RDBMSの排他単位の主流は,行排他です.
 もちろん,行排他ではなく,行をいくつか集めたページを排他単位として採用したRDBMS製品(表1)もあり,「行排他よりも排他制御が速い」ことを利点としています.しかし,排他制御用に情報が行排他よりも少ないので,排他するときは速いかもしれませんが,他の利用者の排他の影響を受けて,排他解除待ちになりやすく,総合的にみれば,必ずしも行排他よりもパフォーマンスの良いシステムが構築できるとは限りません.
 この両者の違いは,パフォーマンス的にどちらが優れているかというよりも,ページ排他では,同時に排他される情報をプログラミング時に制御することはできないという点です.つまり,物理的な1ページに含まれる情報は,情報の発生順でもキーの値順でもなく,RDBMSまかせであるからです.
 これは,プログラミング時には由々しき問題であり,悲観的排他を採用したときには,「なぜ,誰も更新していない情報なのに,排他に引っかかるんだ」と利用者から非難を浴びてしまうかもしれません.

表1:ページを排他単位としたRDBMS製品
製品名 ロックの種類
Oracle7 行排他
Oracle8 行排他
SQL Server 6.5 ページ排他(レコード挿入時は,行排他)
SQL Server 7 行排他(ページ排他からの自動変更機能あり)

本当に排他が必要ですか?

 なぜか,排他の問題にナイーブになりすぎている人がいるようです.業務をろくに分析せず,排他方式を設計したとしても,それは,お客様にとって,満足できる性能や使い勝手を保証しないかもしれません.また,泥縄的な設計が災いして,柔軟性に乏しいシステムを作り上げてしまうかもしれません.設計する上で考慮しなければいけないのは,“排他する”ことではなく,“同時更新時のデータ整合性を確保し,「あれ?変更できていない」とか「変更した値が勝手に変わった」というような不安感を利用者に与えない”ことなのです.そのようなスタンスを欠いて,排他を実現しても,それは,設計者の単なる自己満足だといえるでしょう.不思議なことに,そのような設計者は,本人も自己満足と薄々感づいているのか「それ,排他必要なの? 必要ないのでは?」などと聞かれると感情的になる場合が多いようです.もし,自分の担当するシステムの設計者に質問を投げかけて,感情的な回答が返ってくるようならば,排他処理の周辺を見直してみる必要があるかもしれません.


VB Magazine ライブラリ | Visual Basic WorkGroup
int21 ホームページ | PCDN ホームページ


Copyright (c) 1998 int21 Corporation All Rights Reserved.
For questions or comments, please send mail to: pcdn@int21.co.jp