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

スポンサーリンク

悲観的か、楽観的か。Accessにおける排他制御の「正解」を設計する

Access開発において、多くのエンジニアが「なんとなく」で終わらせている領域がある。それが「DAO.Recordsetの排他制御」だ。

「とりあえずレコードロックをかけておけば安心」という思考停止は、マルチユーザー環境においてシステムを死に至らしめる毒となる。なぜなら、不適切なロックはデッドロックを招き、システムのレスポンスを著しく低下させるからだ。

今日は、業務自動化の最前線で戦う諸君に向けて、DAOを用いた排他制御の勘所を伝授する。

1. 悲観的(Pessimistic) vs 楽観的(Optimistic)の本質

排他制御の選択は、ビジネスロジックの「衝突頻度」と「ユーザーの耐性」によって決まる。

  • 悲観的排他制御 (`dbPessimistic`)
  • 思想: 「編集するなら、編集開始と同時にロックしろ」。
  • メリット: 他ユーザーによる更新の割り込みを物理的に遮断できる。
  • デメリット: ロック時間が長くなりやすく、デッドロックの温床になる。大規模な共有環境では即座にボトルネックとなる。
  • 楽観的排他制御 (`dbOptimistic`)
  • 思想: 「めったに競合はしない。保存する瞬間に競合チェックをすればいい」。
  • メリット: 高パフォーマンス。接続を占有しないため、多人数アクセスに極めて強い。
  • デメリット: 保存直前に「他のユーザーが更新しました」というエラーハンドリングを実装する必要がある。

結論から言おう。現代のAccess開発において、まずは「楽観的排他制御」をデフォルトとせよ。 それでも解決できない、極めて頻繁に競合が発生する特定の業務フローに対してのみ、悲観的制御を検討するのだ。

2. 実践:保守性の高いプロダクションコード

以下のコードは、エラーハンドリングを内包した「楽観的排他制御」のテンプレートだ。これをそのままクラスモジュールや標準モジュールの雛形として活用してほしい。

‘ —————————————————————————
‘ 概要: 楽観的排他制御を用いた安全なレコード更新処理
‘ —————————————————————————
Public Sub UpdateRecordOptimistically(ByVal recordID As Long, ByVal newValue As String)
Dim db As DAO.Database
Dim rs As DAO.Recordset

Set db = CurrentDb

‘ SQLで対象レコードのみをピンポイントで取得(全件取得は厳禁)
Set rs = db.OpenRecordset(“SELECT FROM T_Orders WHERE ID = ” & recordID, dbOpenDynaset, dbSeeChanges)

If Not rs.EOF Then
On Error GoTo ErrHandler

‘ 楽観的排他制御を明示
rs.LockEdits = False

rs.Edit
rs!OrderValue = newValue
rs.Update

MsgBox “更新成功”, vbInformation
End If

Cleanup:
If Not rs Is Nothing Then rs.Close: Set rs = Nothing
Set db = Nothing
Exit Sub

ErrHandler:
‘ 競合エラー(3197等)が発生した際の実践的なハンドリング
If Err.Number = 3197 Then
MsgBox “他のユーザーがこのデータを更新しました。一度画面を閉じて再読み込みしてください。”, vbCritical
Else
MsgBox “予期せぬエラー: ” & Err.Description, vbCritical
End If
Resume Cleanup
End Sub

このコードの「賢い」ポイント

1. `dbSeeChanges` の採用: SQL ServerやAzure SQL等のクライアント/サーバー構成に移行した際、このオプションがないとエラーになる。将来の改修に強いコードを書くのがプロだ。
2. `dbOpenDynaset`: 読み取り専用やスナップショットと混同してはならない。更新が前提ならDynasetが鉄則。
3. エラーハンドリング: 競合を「例外」としてではなく「正常なフローの一部」として捉え、ユーザーに適切なアクションを促している点。

3. 悲観的制御が必要な「唯一のケース」

どうしても悲観的制御(`rs.LockEdits = True`)を使うべきケースは、「極めて短時間に、かつ一度のトランザクションで複数のテーブルを整合性を保ちながら更新し、その間絶対に誰にも触れられたくない場合」に限られる。

しかし、その場合でも以下の鉄則を守れ。

  • ロック時間は最短にする: `rs.Edit`してから`rs.Update`するまでのコード行数を最小化せよ。その間で重い外部APIを叩いたり、複雑な計算をしてはならない。
  • 必ずトランザクションと併用する: `DBEngine.BeginTrans` で囲い、エラー時には必ず `Rollback` すること。ロックしたまま放置してアプリが落ちれば、そのレコードはプロセス終了までロックされ続ける(ゾンビ・ロックの誕生だ)。

4. 最後に:エンジニアの心得

Accessの排他制御を制する者は、マルチユーザー環境のストレスを排除できる。
「動けばいい」というコードは、数ヶ月後の自分や運用担当者を苦しめる負債だ。

  • 競合を想定するUIを作る: 「保存」ボタンを押すまで競合に気づかない設計ではなく、フォームの読み込み時にタイムスタンプを確認する等の工夫を凝らせ。
  • ファイルサーバーの罠: Accessがファイルサーバー上にある場合、OS側のSMBキャッシュ問題で排他制御が正しく機能しないことがある。真に堅牢なシステムを求めるなら、バックエンドをMySQLやSQL Serverへ移行する「逃げ道」を常に意識しておくこと。

設計せよ。コードを書き殴る前に、データがどう動くかを頭の中でシミュレーションするのだ。君たちの書くコードが、明日の業務を少しでも楽にすることを期待している。

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