Access VBAを掌握する極限の知見:DAO.Recordsetの「LockEdits」で排他制御を完全支配せよ
開発現場でよく見かける光景がある。
「複数人で同時にデータを更新したら、知らぬ間に後から上書きした人のデータで消えてしまった」
「レコードを開いた瞬間に画面が固まり、他のユーザーが全く作業できなくなった」
Accessデータベース(Jet/ACEエンジン)を使った業務システムにおいて、データの整合性とユーザーの利便性(操作性)のバランスを取ることは、アーキテクトとしての腕の見せ所だ。この二律背反をコントロールする鍵が、DAOにおける `LockEdits` プロパティ である。
今回は、悲観的排他制御と楽観的排他制御の本質を見極め、現場で絶対に破綻しない堅牢なレコードロック設計を伝授する。
—
1. なぜ「既定の動作」に頼るななのか?(排他制御の基本思想)
AccessのフォームやVBAでレコードセットを操作する際、ロックの仕組みを意識していないコードは、時限爆弾を抱えているようなものだ。
DAOの `Recordset` オブジェクトにおけるロック方式には、以下の2種類が存在する。
1. 悲観的排他制御(Pessimistic Locking): `LockEdits = True`
- 思想: 「人間は間違いを起こす。誰もデータを触らせないようにしよう」
- 挙動: レコードの編集メソッド(`Edit`)を呼び出した瞬間から、`Update` または `Close` するまで、該当レコードが他者からロックされる。
- メリット: データの競合・上書きが絶対に発生しない。
- デメリット: ロック期間が長くなるため、他のユーザーが待たされ、スループットが劇的に低下する。
2. 楽観的排他制御(Optimistic Locking): `LockEdits = False`(既定値)
- 思想: 「競合は滅多に起きない。更新する瞬間だけ確認しよう」
- 挙動: 編集開始時点ではロックせず、`Update` を実行する直前に「他のユーザーに書き換えられていないか」を検証する。書き換えられていれば実行時エラー(トラップ可能なエラー)が発生する。
- メリット: 同時接続性に優れ、システムが軽快に動作する。
- デメリット: 競合が発生した際のエラーハンドリングをコード側で確実に実装しなければならない。
チーフアーキテクトの戒め:
「とりあえず動くから」とデフォルトのまま放置し、重要データを吹き飛ばす事故が後を絶たない。基幹データのマスタメンテナンスには「悲観的」、トランザクションの高速処理や同時アクセスが多い画面には「楽観的」と、業務特性に合わせてコードで明示的に制御するのがプロの作法である。
—
2. 現場で即採用できるプロダクションコード
以下のコードは、実務でそのまま流用できる「安全なエラーハンドリング付きレコード更新処理」のテンプレートだ。
ここでは、楽観的排他制御(`LockEdits = False`)を採用し、万が一の競合発生時(他のユーザーが先に更新していた場合)に優しくリカバリーする設計を実装している。
Option Compare Database
Option Explicit
‘ =========================================================================
‘ プロシージャ名: UpdateCustomerStatus
‘ 概要 : 顧客ステータスを楽観的排他制御で安全に更新する
‘ 引数 : lngCustomerID – 対象の顧客ID
‘ strNewStatus – 新しいステータス
‘ 戻り値 : 成功時は True、失敗時は False
‘ =========================================================================
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
On Error GoTo ErrorHandler
‘ データベース参照の取得(CurrentDbは都度変数に格納して使い回すのが鉄則)
Set db = CurrentDb
‘ 対象レコードを特定するSQL
strSQL = “SELECT CustomerID, Status, LastUpdated FROM T_Customers WHERE CustomerID = ” & lngCustomerID
‘ 【重要】LockEditsを明示的にFalse(楽観的排他制御)に設定してオープン
‘ ※dbOpenDynasetを指定することが必須条件
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset, dbDenyWrite) ‘ ※用途に応じたオプション
‘ ↑ここは通常のDynasetにするため修正:
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
rs.LockEdits = False ‘ 楽観的ロック(念のため明示)
If rs.RecordCount = 0 Then
MsgBox “対象のレコードが見つかりません。(ID: ” & lngCustomerID & “)”, vbExclamation, “更新エラー”
GoTo CleanUp
End If
‘ 編集モードの開始
rs.Edit
‘ 値の更新
rs!Status = strNewStatus
rs!LastUpdated = Now()
‘ 更新の確定(ここで競合チェックが行われる)
rs.Update
UpdateCustomerStatus = True
GoTo CleanUp
ErrorHandler:
‘ 3218: ファイルがロックされています
‘ 3260: 他のユーザーまたはプロセスがこのレコードを更新中です(競合発生)
If Err.Number = 3260 Or Err.Number = 3218 Then
MsgBox “他のユーザーがすでにこのデータを更新しています。” & vbCrLf & _
“最新のデータを取得し直してから、再度操作を行ってください。”, vbCritical, “排他制御エラー(競合)”
‘ 変更を破棄してレコードセットの状態を復元
If rs.EditMode <> dbEditNone Then rs.CancelUpdate
Else
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “システムエラー”
End If
UpdateCustomerStatus = False
CleanUp:
‘ オブジェクトの確実な解放(メモリリークとロックの残留を防ぐ)
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
Set db = Nothing
End Function
—
3. 実務で絶対に外せない設計上の注意点
このコードとアーキテクチャを運用する上で、Access特有の「罠」を避けるための知見を共有する。
① `dbOpenDynaset` との組み合わせが絶対条件
`LockEdits` プロパティは、テーブル直読み込み(`dbOpenTable`)や静的なスナップショット(`dbOpenSnapshot`)では意味を持たない。動的集合である `dbOpenDynaset` に対してのみ機能する。ここを間違えると、意図したロック挙動にならないため注意せよ。
② ネットワーク共有環境(ファイルサーバー)での限界
Access(ACEエンジン)は、ファイルサーバー上に `.accdb` を配置して複数人で共有するアーキテクチャ上、ネットワークの遅延やパケットロストの影響を強く受ける。
- 悲観的ロック(`LockEdits = True`)を長時間かけっぱなしにする設計は、ファイルロック競合を引き起こし、最悪の場合データベースの破損(コラプション)に直結する。
- 可能な限り楽観的ロックを採用し、ロック保持時間をミリ秒単位で最小化する設計思想が、システムをクラッシュから守る盾となる。
③ オブジェクト変数のスコープとクローズの徹底
VBAにおけるDAOオブジェクト(`Database` や `Recordset`)は、プロシージャ終了時に自動解放される仕様になっている。しかし、排他制御においては「いつロックが解放されるか」の制御が生死を分ける。
処理が終わったら即座に `.Close` し、`Set rs = Nothing` で参照を切る。この規律をチーム全体で徹底してほしい。
—
4. チーフアーキテクトからの提言
システム開発において、動くコードを書くことはスタートラインに過ぎない。
「同時に2人のユーザーがボタンを押したらどうなるか?」
この問いに対して、例外処理と排他制御のロジックを淀みなく組み込めるかどうかが、プロとアマの分岐点である。
今回解説した `LockEdits` のコントロールをマスターすれば、Accessのポテンシャルを極限まで引き出し、スケーラブルで堅牢な業務システム構築が可能になる。あなたのコードベースに早速組み込み、ワンランク上のアーキテクチャを実現してほしい。
