ADOを使ったレコード単位の排他ロックについて

オートナンバーでなく,主キー値を自動取得したい時に
複数のユーザーが使用するような環境だと

採番テーブル

というものを作って,そこに現在の主キーの最大値を入れて置くようなことはよくあります。
その時,その値を更新する時,排他状態にしないといけません。

採番テーブルは,以下のようなものを前提にします。

 テーブル名: 採番_A

ID         長整数型
MaxValue   長整数型   '現在の最大の主キー値を保存
Dummy      長整数型

採番テーブルのレコードは,ID: 1 の1レコードのみ。

主キー値は単純に +1 ずつ増加。

※ 主キーが "AAA-000001" のように組み合わさっている場合は,
下の fGetNextID に引数を入れて
WHERE句でそれぞれの最大値を拾い出すようにします。
Nz(rs!MaxValue) + 1 のようなものでなく,
少しロジックも変更します。

 

次の主キー値を取得する関数(例えば,fGetNextID_A)を作っておいて,

Private Sub Form_Current()

    If Me.NewRecord Then
        Me!主キーフィールド.DefaultValue = fGetNextID_A
        'テキスト型の時は,
        'Me!主キーフィールド.DefaultValue = """" & fGetNextID_A & """"
    End If

End Sub

のようにします。

まず,DAOで説明すると

Private Function fGetNextID_A() As Long

    Dim db As DAO.Database
    Dim rs As DAO.Recordset
    Dim strSQL As String
    Dim lngNextID As Long

    strSQL = "SELECT * FROM 採番_A WHERE ID = 1"
    Set db = CurrentDB
    Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
  
    rs.Edit                    '<--ここでロックがかかる。
    lngNextID = Nz(rs!MaxValue) + 1
    rs!MaxValue = lngNextID
    rs.Update

    rs.Close
    Set rs = Nothing      ' この行は無くてもかまいません。
    db.Close
    Set db = Nothing      ' この行は無くてもかまいません。

    fGetNextID_A = lngNextID

End Function

のようにした場合,DAOだとデフォルトで
rs.Edit のところで排他的ロックがかかります。
これで万が一他のユーザーがこのテーブルにアクセスして
同時に .Edit しようとしてもできません。

一方,ADOで行うと,
rs.Open 時に,adLockPessimistic を指定しても,なぜか,
以下の rs.Update のところで初めてロックがかかるため
非常に具合が悪いので,ADOでは難しいと書きました。

rs.Update のところで一瞬だけロック状態になるのを
オプティミスティック・ロック (楽観的ロック/共有的ロック) といいます。

Private Function fGetNextID_A() As Long

  Dim cn As ADODB.Connection
  Dim rs As ADODB.Recordset
  Dim strSQL As String
    
  strSQL = "SELECT * FROM 採番_A WHERE ID = 1"
  Set rs = New ADODB.Recordset
  Set cn = CurrentProject.Connection
  rs.Open strSQL, cn, adOpenKeyset, adLockPessimistic, adCmdText
  
  'rs.Edit (注) ADOには,明示的にrs.Editはないが,それでも
  rs!Dummy = rs!Dummy + 1    '本来ならここでロックされる予定
  lngNextID = Nz(rs!MaxValue) + 1
  rs!MaxValue = lngNextID
  rs.Update                  '実際は,ここでロックされる
  
  rs.Close
  Set rs = Nothing      ' この行は無くてもかまいません。
  cn.Close
  Set cn = Nothing      ' この行は無くてもかまいません。

End Function

 

ADOのヘルプより

 adLockPessimistic          2 

レコード単位の排他的ロックを示します。プロバイダは、
レコードを確実に編集するための措置を行います。通常は、
編集直後のデータ ソースでレコードをロックします。

adLockPessimistic の説明からも
最初に編集した地点でレコードがロックされるはずなのに
実際は,ロックされずに楽観的ロック/共有的ロック(adLockOptimistic)
になってしまいます。

SQLServerやMSDE相手だと他の方法(※)があるのですが,
Accessだと難しいのか? ... と考えていました。

  ※ "SELECT * FROM 採番_A WITH (UPDLOCK) WHERE ID = 1"
     のように WITH (UPDLOCK) を付けます。
     2001年11月号のVBMagazineにも説明があります。 

今まで,ずっと ADO や JET のヘルプで
何か手がかりはないのか? と探していました。
昨夜,Access側のヘルプを見ていて以下の記述に気が付きました。
CurrentProjectオブジェクトのConnectionプロパティの下の方に

  3. MSDataShape では Recordset.LockType = adLockOptimistic、
     adLockBatchOptimistic、または adLockReadOnly (既定値) 
     をサポートします。adLockPessimistic を設定すると、
     adLockOptimistic に変更されます。

のように書いてあります。

実は,Access2000から,ADOJET Database の間に
Data Shaping Service (上記のMSDataShapeのこと)
というものが入っています。参考資料1 参考資料2

(テーブル表示で,リンクされた多側のテーブルを
いっしょに表示できるようにするためのもので,
Access2000からテーブルで + をクリックすると
開くようになった例の機能です。)

そして,CurrentProject.Connection を使って取得すると
この DataShapingService との間の Connection のコピー
が返ってきます。
つまり,DataShapingService との接続情報が返ってきます。よって,

 Set cn = CurrentProject.Connection
rs.Open strSQL, cn, adOpenKeyset, adLockPessimistic, adCmdText

のように,Pessimisticで指定しても,上記の

  「adLockPessimistic を設定すると
   adLockOptimistic に変更されます。」

という恐ろしい暗黙の変換によって

共有的ロック (Optimistic Lock)

になってしまっていたのです ... なんてことか!

[回避策]

JET Databaseとの基本接続情報は,

CurrentProject.BaseConnectionString

で取り出せるので,それをConnectionオブジェクトの接続文字列に設定して,.Openします。

Private Function fGetNextID_A() As Long

  Dim cn As ADODB.Connection
  Dim rs As ADODB.Recordset
  Dim strSQL As String
    
  strSQL = "SELECT * FROM 採番_A WHERE ID = 1"
  Set cn = New ADODB.Connection
  cn.ConnectionString = CurrentProject.BaseConnectionString
  cn.Open
  Set rs = New ADODB.Recordset
  rs.Open strSQL, cn, adOpenKeyset, adLockPessimistic, adCmdText
  
  'rs.Edit (注) ADOには,明示的にrs.Editはないが,
  rs!Dummy = rs!Dummy + 1    'ちゃんとここでロックされる
  lngNextID = Nz(rs!MaxValue) + 1
  rs!MaxValue = lngNextID
  rs.Update
  
  rs.Close
  Set rs = Nothing      ' この行は無くてもかまいません。
  cn.Close
  Set cn = Nothing      ' この行は無くてもかまいません。

End Function

 

 

 

戻る

ホームへ

 

Published: 2001-10-19

Last Updated: 2013-06-14

つくれますの部屋

Copyright(C) 2001 Yasuharu Takahashi, All Rights Reserved.