こんにちは!業務自動化エンジニアの先輩です。
Accessのシステムを一人で作成・テストしている時は完璧に動いていたのに、職場のチームみんなで使い始めた途端、「書き込みの競合エラーが出る!」「誰かが開いていて更新できない!」というトラブルに遭遇したことはありませんか?
マクロの記録から一歩踏み出し、VBAで複数ユーザーが同時にアクセスするシステムを構築する際、必ず理解しておかなければならない超重要概念が「排他制御(ロック処理)」です。
今回は、Access VBAにおけるデータ操作の要である`DAO.Recordset`の`LockEdits`プロパティにスポットを当てます。
「悲観的ロック」と「楽観的ロック」の違いを、イメージしやすい図解とともに分かりやすく解説しますね。
ここをクリアすれば、Access VBAの基本はバッチリですよ!一緒にマスターしていきましょう。
—
1. 排他制御の基本:2つのロック方式を知ろう
複数人が同時に同じテーブルのデータを書き換えようとした時、データの整合性を守るための仕組みが「排他制御」です。Access VBAの`DAO.Recordset`では、`LockEdits`というプロパティを使ってこの制御を切り替えます。
排他制御には、大きく分けて「悲観的(ペシミスティック)ロック」と「楽観的(オプティミスティック)ロック」の2つがあります。
試着室とレジで覚えるイメージ図
それぞれの違いを日常生活に例えてみましょう。
【悲観的ロック (LockEdits = True)】=「試着室」スタイル
[ユーザーA] .Edit開始 ──> 🔒 鍵をかける!(他の人は入れない)
│ (データ編集・思考中…)
[ユーザーA] .Update ──> 🔓 鍵を開ける
※他のユーザーBは、Aが終わるまで絶対に編集を開始できません。
【楽観的ロック (LockEdits = False)】=「スーパーのレジ」スタイル
[ユーザーA] .Edit開始 ──> 🔓 自由に見る・編集する(鍵はかけない)
[ユーザーB] .Edit開始 ──> 🔓 自由に見る・編集する
[ユーザーA] .Update ──> 🔒 一瞬だけ鍵をかけて保存!成功!
[ユーザーB] .Update ──> ❌ 「先にAさんに変更されました!」エラー発生
- 悲観的ロック(`LockEdits = True`)
「どうせ他のユーザーと衝突するだろう」と悲観的に考え、`.Edit`(編集開始)を呼び出した瞬間にレコードをロックします。
- 楽観的ロック(`LockEdits = False`)
「めったに衝突しないだろう」と楽観的に考え、`.Update`(保存時)のまさにその一瞬だけロックします。なお、DAOのデフォルト(初期値)はこの「楽観的ロック(`False`)」です。
—
2. どちらを使うべき?それぞれのメリット・デメリット
状況に応じて使い分けるのがプロの技です。以下の表で整理してみましょう。
| 項目 | 悲観的ロック (`LockEdits = True`) | 楽観的ロック (`LockEdits = False`) |
| :— | :— | :— |
| ロックのタイミング | `.Edit` を実行した瞬間 | `.Update` を実行した瞬間 |
| 安全度 | 高(確実に自分が上書きできる) | 中(衝突時に保存エラーになる) |
| システムの処理速度 | 低(他の人が待たされるため) | 高(並行処理に強い) |
| おすすめの場面 | 在庫カウントの更新、注文受付など絶対に重複したくない重要処理 | マスターデータの閲覧・修正、ログの記録など競合頻度が低い処理 |
—
3. 実践!コードで学ぶ `LockEdits` の使い方
それでは、実際のVBAコードを見てみましょう。コピー&ペーストして使える実践的なコードを用意しました。
① 楽観的ロック (`LockEdits = False`) の実装例
一般的な処理では、パフォーマンスに優れた楽観的ロックを使用します。他のユーザーと衝突した場合のエラーハンドリング(エラー番号 `3197` や `3260` など)を入れておくのがプロの作法です。
Sub UpdateCustomer_Optimistic()
On Error GoTo ErrorHandler
Dim db As DAO.Database
Dim rs As DAO.Recordset
‘ 現在のデータベースを取得
Set db = CurrentDb
‘ レコードセットを開く(ダイナセット形式)
Set rs = db.OpenRecordset(“tbl_Customer”, dbOpenDynaset)
‘ ————————————————–
‘ 楽観的ロックを設定 (既定値ですが明示的に記述)
‘ ————————————————–
rs.LockEdits = False
‘ 目的のレコードを検索
rs.FindFirst “CustomerID = 101”
If Not rs.NoMatch Then
‘ 編集を開始(この時点ではまだロックされません)
rs.Edit
‘ データを書き換え
rs!CustomerName = “株式会社ネクスト・イノベーション”
rs!LastUpdate = Now()
‘ データの保存(この一瞬だけロックがかかります)
rs.Update
MsgBox “楽観的ロックでの更新が完了しました!”, vbInformation, “成功”
Else
MsgBox “対象の顧客データが見つかりませんでした。”, vbExclamation
End If
CleanExit:
‘ ————————————————–
‘ オブジェクトのクリーンアップ(メモリ解放とロック解除)
‘ ————————————————–
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
Set db = Nothing
Exit Sub
ErrorHandler:
‘ エラー 3197: 別のユーザーがデータを変更した
If Err.Number = 3197 Then
MsgBox “他のユーザーが同時にこのデータを更新しました。” & vbCrLf & _
“一度画面を再読み込みして最新状態を確認してください。”, vbCritical, “競合エラー”
Else
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “エラー ” & Err.Number
End If
Resume CleanExit
End Sub
② 悲観的ロック (`LockEdits = True`) の実装例
在庫管理などで、「自分が画面で編集ボタンを押している間は、絶対に他の人に横取りされたくない!」という重要な処理に使います。
Sub UpdateStock_Pessimistic()
On Error GoTo ErrorHandler
Dim db As DAO.Database
Dim rs As DAO.Recordset
Set db = CurrentDb
Set rs = db.OpenRecordset(“tbl_Stock”, dbOpenDynaset)
‘ ————————————————–
‘ 悲観的ロックを設定
‘ ————————————————–
rs.LockEdits = True
rs.FindFirst “ProductID = ‘P-001′”
If Not rs.NoMatch Then
‘ 【重要】.Editを実行した瞬間に、対象レコード(またはページ)にロックがかかります!
rs.Edit
‘ 在庫数を1つ減らす処理
rs!StockQuantity = rs!StockQuantity – 1
rs!LastUpdate = Now()
‘ データを保存(ロックが解除されます)
rs.Update
MsgBox “悲観的ロックでの在庫更新が完了しました!”, vbInformation, “成功”
End If
CleanExit:
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
Set db = Nothing
Exit Sub
ErrorHandler:
‘ エラー 3260: レコードが現在他のユーザーによってロックされています
If Err.Number = 3260 Then
MsgBox “現在、他のユーザーがこの商品を更新中です。” & vbCrLf & _
“時間を置いてから再度お試しください。”, vbExclamation, “ロック中”
Else
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical
End If
Resume CleanExit
End Sub
—
4. 陥りやすい罠と先輩エンジニアからのアドバイス
初心者の方がよくハマる罠と、実務で役に立つ極意を2つ共有しておきますね。
① 「レコードレベルのロック」オプションを確認しよう
`LockEdits = True`(悲観的ロック)に設定しても、Accessのデフォルト設定では「ページレベルのロック(約2KB単位)」がかかってしまうことがあります。
つまり、目的の1件だけでなく、「その近くにある関係ない他のレコードまで巻き添えでロックされてしまう」のです!
これを防ぐために、以下の設定を確認しておきましょう。
1. Accessのメニューから [ファイル] > [オプション] を開きます。
2. [クライアントの設定] を選択します。
3. [詳細設定] セクションにある 「レコード レベルのロックを使用して開く」 にチェックが入っていることを確認してください。
② 明示的なクリーンアップ(`rs.Close` / `Set rs = Nothing`)を怠らない
コードの最後にある `rs.Close` や `Set rs = Nothing` は、ただの「おまじない」ではありません。
悲観的ロックをかけている場合、プログラムが途中で止まったり、オブジェクトが開放されずに残ったりすると、データベース上に永久に鍵がかかったまま(デッドロック状態)になってしまう危険があります。
どんなエラーが起きても必ず `CleanExit` のような終了処理を通過させ、オブジェクトを確実に解放する癖をつけましょう!
—
まとめ:排他制御を制するものはAccessシステムを制す
今回はDAO.Recordsetの`LockEdits`プロパティを使った排他制御の最適化について解説しました。
- `LockEdits = False`(楽観的ロック):速度重視。一般的なシステム向け(デフォルト)。
- `LockEdits = True`(悲観的ロック):安全重視。在庫・予約など絶対に被らせたくない処理向け。
この違いを意識してコードが書けるようになれば、あなたはもう初心者脱出です!複数ユーザーがストレスなく快適に使える、ワンランク上の高品質なAccessシステムを構築することができますよ。
分からないことがあれば、いつでもコードを見直しに来てくださいね。応援しています!
