【実務・中級編】【中級】DAO.Recordsetの「LockEdits」プロパティによる、悲観的排他制御と楽観的排他制御の使い分け – Access VBA解析バイブル

スポンサーリンク

【中級】DAO.Recordsetの「LockEdits」徹底解説:悲観的・楽観的排他制御の極限最適解

マルチユーザー環境のAccessデータベース開発において、最も見落とされがちであり、かつ本番稼働後にシステムを致命的なデッドロックやデータ破損へと導く魔物が潜んでいる。それが「排他制御(Concurrency Control)」だ。

画面を開いて編集しようとした瞬間に他のユーザーをブロックするのか、それとも書き込みの瞬間まで自由を与え、衝突時のみ例外を検知するのか。
この選択を誤れば、業務効率は劇的に低下するか、あるいは「知らぬ間に他人のデータを上書きして消してしまった」という大惨事を引き起こす。

今回は、DAO.Recordsetの `LockEdits` プロパティに焦点を当て、悲観的排他制御と楽観的排他制御のメカニズム、そして現場で即座に使える堅牢なエラーハンドリングの実装パターンを、チーフアーキテクトの視点から授けよう。

1. なぜ「なんとなく動くコード」では現場で破綻するのか

Access(Jet / ACEエンジン)は、ファイルサーバー上の共有フォルダに置かれることが多々ある。このアーキテクチャにおいて、複数人が同時に同じレコードを更新しようとしたとき、データベースエンジンはデフォルトのままでは無防備だ。

素人が書いたコードによくあるのが、エラーハンドリングを一切せずに `Recordset` を開いて `.Edit` から `.Update` を叩くだけの代物だ。
これでは、ユーザーAが編集中にユーザーBが同じレコードを上書き保存した場合、ユーザーAの変更が闇に葬られる(ロストアップデート)。あるいは、ネットワークの瞬断やタイムアウトと相まって、Access特有の「予約語エラー」や「書き込みロックエラー」が多発し、現場から悲鳴が上がる。

この状況を制御するのが、`Recordset` の `LockEdits` プロパティ である。

2. LockEdits の二大思想:悲観的 vs 楽観的

DAOにおける `LockEdits` には、以下の2つの定数を設定する。

1. `dbLockEdited` (悲観的排他制御 / Pessimistic)

  • 思想: 「人は裏切る。誰も信じるな」
  • 挙動: `.Edit` メソッドを呼び出した瞬間から、対象レコード(またはページ)を物理的にロックする。他のユーザーはそのレコードを編集できなくなる。
  • メリット: 更新時の競合が絶対に起きない。
  • デメリット: ユーザーが編集画面を開いたまま離席したりすると、他のユーザーの業務が完全にストップする(スケーラビリティの崩壊)。

2. `dbLockOptimistic` (楽観的排他制御 / Optimistic)

  • 思想: 「誰もが善良で、同時に同じ場所を触ることは稀だ。信じよう」
  • 挙動: データを読み込み、編集している間はロックしない。`.Update` メソッドを実行した瞬間に、他のユーザーによってデータが改変されていないかを検証し、改変されていればエラーを発生させる。
  • メリット: 同時実行性が高く、システムが軽快に動く。
  • デメリット: 競合が発生した際のエラーハンドリング(再試行や変更破棄のロジック)を必ず実装しなければならない。

アーキテクトの判断基準

社内限定の小規模なツールで、どうしてもデータ競合を防ぎたい場合は悲観的ロックも選択肢に入る。しかし、モダンな業務システムにおいては、基本的に「楽観的排他制御 (`dbLockOptimistic`)」を採用し、競合時のエラーをコードで美しくハンドリングする設計が鉄則である。

3. 【プロダクションコード】堅牢な排他制御とエラーハンドリングの実装

それでは、実務でそのまま使える堅牢なVBAコードを提示する。
このコードでは、楽観的排他制御を採用し、万が一のデータ競合(実行時エラー: 3188 または 3260 など)が発生した際に、システムがクラッシュせず、ユーザーに优雅(エレガント)に対応を促す構造にしている。

‘ ==============================================================================
‘ プロシージャ名 : UpdateCustomerStatus
‘ 概要 : 顧客ステータスを楽観的排他制御を用いて安全に更新する
‘ 引数 : lngCustomerID – 対象顧客ID
‘ : strNewStatus – 新しいステータス
‘ 戻り値 : Boolean (成功時は True)
‘ ==============================================================================
Public Function UpdateCustomerStatus(ByVal lngCustomerID As Long, ByVal strNewStatus As String) As Boolean
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim strSQL As String
Const conErrDataChanged As Long = 3188 ‘ 例: 他のユーザーによって変更された際のエラー番号(環境により3260等も考慮)
Const conErrLocked As Long = 3260 ‘ 別のユーザーがロック中

On Error GoTo ErrorHandler

UpdateCustomerStatus = False
Set db = CurrentDb()

‘ 対象レコードを特定するSQL
strSQL = “SELECT CustomerID, Status, UpdateDate, UpdatedBy FROM T_Customers WHERE CustomerID = ” & lngCustomerID

‘ 【重要】LockEditsに dbLockOptimistic を明示的に指定して開く
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset, dbSeeChanges, dbLockOptimistic)

If rs.EOF And rs.BOF Then
MsgBox “指定されたレコードが見つかりません。”, vbExclamation, “排他制御エラー”
GoTo CleanUp
End If

‘ 編集モードへ移行(ここではまだロックはかからない)
rs.Edit

‘ データの書き換え
rs!Status = strNewStatus
rs!UpdateDate = Now()
rs!UpdatedBy = Environ$(“UserName”)

‘ 【核心】Updateメソッドを実行した瞬間に競合チェックが行われる
rs.Update

UpdateCustomerStatus = True
MsgBox “ステータスの更新が完了しました。”, vbInformation, “処理成功”

CleanUp:
‘ オブジェクトの確実な解放(メモリリークの根絶)
On Error Resume Next
If Not rs Is Nothing Then rs.Close: Set rs = Nothing
If Not db Is Nothing Then Set db = Nothing
Exit Function

ErrorHandler:
Select Case Err.Number
Case conErrLocked, conErrDataChanged, 3167 ‘ レコードがロックされている、または変更された
MsgBox “他のユーザーがこのデータを既に変更またはロックしています。” & vbCrLf & _
“最新のデータを取得し直してやり直してください。”, vbCritical, “排他制御競合”
‘ 必要に応じて rs.CancelUpdate で変更を破棄
rs.CancelUpdate
Resume CleanUp

Case Else
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“Error: ” & Err.Number & ” – ” & Err.Description, vbCritical, “システムエラー”
Resume CleanUp
End Select
End Function

4. コードの深層解説:プロフェッショナルの技術的こだわり

上記のコードには、単なる「動くコード」を超えた、実務でのトラブルを防ぐための緻密な設計が組み込まれている。

① `dbSeeChanges` の同時付与

リモートのSQL Server等(ODBCリンクテーブル)をDAOで操作する場合や、ACEエンジンの特性上、ダイナセットを開く際には `dbSeeChanges` を同時に付与することが必須だ。これを忘れると、他のユーザーが変更した瞬間にトラップできない致命的な例外が発生する。

② `rs.CancelUpdate` による安全なステータス復帰

エラーハンドラー内で競合を検知した際、`rs.CancelUpdate` を挟むことで、Recordsetオブジェクト内をクリーンな状態に戻し、予期せぬメモリ上のゴミデータを残さないようにしている。

③ 徹底的なオブジェクトの解放

VBAにおいて `Set rs = Nothing` と `Set db = Nothing` を怠ると、Accessの内部メモリ(Jetエンジン)にリソースが残り続け、いわゆる「メモリ肥大化によるフリーズ現象」を引き起こす。`On Error Resume Next` と組み合わせた `CleanUp` ラベリングは、プロダクションコードの基本作法である。

5. まとめ:プロフェッショナルとしての心得

Access VBAは「誰でも簡単に書ける」がゆえに、設計の優劣がそのままシステムの寿命に直結する。
マルチユーザー環境を考慮せず、デフォルト設定のままコードを量産することは、将来の爆弾を自ら仕掛けているのと同義だ。

  • 基本は `dbLockOptimistic` による楽観的排他制御。
  • `Update` 時に発生する競合エラーを必ずトラップし、ユーザーに優しいリカバリを実装する。
  • リソースのライフサイクル(Open ⇄ Close / Nothing)を完璧にコントロールする。

この知見をあなたのプロジェクトにインストールした瞬間から、Accessアプリケーションの信頼性は劇的に向上する。現場をあっと言わせる堅牢なシステムを、その手で構築してほしい。

タイトルとURLをコピーしました