はじめに:複数ユーザーで動かすAccessアプリの「最大の壁」を越えよう!
こんにちは!今日もAccess VBAの世界へようこそ。
VBAの基本文法を覚え、マクロ機能から脱却してコードでデータベースを操作できるようになってくると、次に挑戦したくなるのが「社内の複数メンバーで同時に使える業務システム」の開発ですよね。
しかし、ここで多くの開発者が必ずぶつかる大きな壁があります。それが「書き込み競合(同時に同じデータを変更してしまう事故)」です。
- 「Aさんが画面を開いて編集している間に、Bさんが同じデータを書き換えて上書きしてしまった…」
- 「突然『データを変更できません』というエラーが出て、ユーザーが混乱してしまった…」
こうした問題をスマートに解決するのが、DAO(Data Access Objects)の `Recordset` に備わっている `LockEdits`(ロック・エディッツ)プロパティ です。
今回は、複数ユーザー環境で必須となる「悲観的排他制御(Pessimistic Locking)」と「楽観的排他制御(Optimistic Locking)」の仕組みを、どこよりも分かりやすく解説します。
ここをクリアすれば、単なる「VBAが書ける人」から「実用的な業務システムを設計できる本物のエンジニア」へステップアップできますよ!焦らず一歩ずつ進めていきましょう。
—
1. なぜ「排他制御(レコードロック)」が必要なのか?
まずは、なぜ排他制御が必要なのか、図解的なイメージで整理してみましょう。
例えば、商品の在庫数を管理するデータベースがあり、現在の在庫が「10個」だとします。
【同時更新の事故のメカニズム】
[ データベースの初期状態:在庫 10個 ]
│
├──────────────────────────┐
▼ ▼
【Aさんの端末】 【Bさんの端末】
データを読み込む(在庫10) データを読み込む(在庫10)
│ │
3個売れたので「7」に計算 2個売れたので「8」に計算
│ │
▼ │
[ Aさんが保存 ] │
データベースの在庫は「7」になる │
▼
[ Bさんが遅れて保存 ]
データベースの在庫は「8」になる!?
最後に保存したBさんの処理によって、Aさんが更新した「3個売れた」という事実が消え去ってしまいました。これが「更新紛失(Lost Update)」と呼ばれる深刻なデータ破損トラブルです。
これを防ぐために「いま私がこのデータを操作中だから、他の人は触らないでね!」と鍵をかける仕組み、それが排他制御(ロック)です。
—
2. 鍵の掛け方は2通り!`LockEdits` プロパティとは?
Access VBAでレコードセット(データの集合体)を扱う際、制御のキーを握るのが `DAO.Recordset.LockEdits` プロパティです。
このプロパティには `True` または `False` を指定することで、ロックのタイミング(思想)を切り替えることができます。
| 設定値 | 排他制御の方式 | 概要・例え |
| :— | :— | :— |
| `True` (初期値) | 悲観的排他制御
(Pessimistic Locking) | 「絶対にバッティングする!」と悲観的に考える方式。
編集を開始した瞬間(`.Edit`)にカギをかけ、他の人の編集を一切ブロックします。
(試着室に入った瞬間にドアの鍵を閉めるイメージ) |
| `False` | 楽観的排他制御
(Optimistic Locking) | 「めったにバッティングしないだろう」と楽観的に考える方式。
編集中の鍵掛けはせず、最後の保存の瞬間(`.Update`)だけ衝突がないか確認します。
(レジに並ぶまで商品はキープせず、会計時に在庫チェックするイメージ) |
それぞれの特徴と実装コードを詳しく見ていきましょう。
—
3. 【深掘り1】悲観的排他制御(`LockEdits = True`)
仕組みと特徴
`.Edit` メソッドを実行したその瞬間にレコードをロックします。保存(`.Update`)またはキャンセル(`.CancelUpdate`)が完了するまで、他のユーザーはそのレコードを編集できません。
- メリット: 自分が編集を開始できさえすれば、絶対にデータの横取り(更新衝突)が起きない。
- デメリット: 編集中の長い時間、他人が待たされる。ネットワークの負荷が高まりやすい。
実装コード(VBA)
悲観的排他制御では、誰かがすでにロックしているレコードに `.Edit` を試みると、エラー 3260(他ユーザーによるロック)が発生します。これをトラップ(補獲)するのがプロのコードです。
Option Explicit
‘ =================================================================
‘ 悲観的排他制御(Pessimistic Locking)の実装例
‘ 目的: 他のユーザーに絶対に割り込まれたくない厳格な更新処理
‘ =================================================================
Public Sub UpdateCustomerPessimistic(ByVal customerID As Long, ByVal newPhone As String)
On Error GoTo ErrHandler
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim strSQL As String
Set db = CurrentDb()
‘ 対象の顧客レコードを取得
strSQL = “SELECT FROM T_顧客 WHERE 顧客ID = ” & customerID
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
‘ レコードが存在するか確認
If Not rs.EOF Then
‘ — 悲観的排他制御を明示的に有効化 —
rs.LockEdits = True
‘ 【重要】ここで他人がすでにロックしているとエラー 3260 が発生します
rs.Edit
‘ データの変更処理
rs!電話番号 = newPhone
‘ データの保存(ここでロックが解除されます)
rs.Update
MsgBox “電話番号を安全に更新しました!”, vbInformation, “完了”
Else
MsgBox “該当の顧客が見つかりませんでした。”, vbExclamation, “警告”
End If
ExitHandler:
‘ ————————————————————-
‘ メモリリークを防ぐため、オブジェクトの後始末(ライフサイクル管理)
‘ ————————————————————-
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
Set db = Nothing
Exit Sub
ErrHandler:
Select Case Err.Number
Case 3260 ‘ 他のユーザーによってレコードがロックされている場合
MsgBox “現在、他のユーザーがこのデータを編集中のため更新できません。” & vbCrLf & _
“しばらく時間を置いてから再度お試しください。”, vbCritical, “排他エラー”
Case Else
MsgBox “予期せぬエラーが発生しました(” & Err.Number & “): ” & Err.Description, vbCritical, “エラー”
End Select
Resume ExitHandler
End Sub
—
4. 【深掘り2】楽観的排他制御(`LockEdits = False`)
仕組みと特徴
`.Edit` を実行してもロックをかけません。データを書き換えて、最後の `.Update` を実行した一瞬だけ データベースを確認します。
もし「自分がデータを読み込んでから保存するまでの間」に他人がデータを書き換えていた場合、Accessは保存を拒否し、エラー 3197(他者による変更検出)を発生させます。
- メリット: ロック時間が一瞬のため、複数人での同時閲覧・更新パフォーマンスが非常に高い。
- デメリット: 保存の段階になって初めて「あ、他の人に書き換えられていた…」と気づくため、入力をやり直してもらう処理(再試行ロジック)が必要になる。
実装コード(VBA)
楽観的排他制御では、保存時(`.Update`)のエラーを検知し、ユーザーに対処を選ばせる(上書きするか、キャンセルするか)ような設計が美しく親切です。
Option Explicit
‘ =================================================================
‘ 楽観的排他制御(Optimistic Locking)の実装例
‘ 目的: パフォーマンスを優先し、競合発生時のみ適切にハンドリングする処理
‘ =================================================================
Public Sub UpdateCustomerOptimistic(ByVal customerID As Long, ByVal newPhone As String)
On Error GoTo ErrHandler
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim strSQL As String
Set db = CurrentDb()
strSQL = “SELECT FROM T_顧客 WHERE 顧客ID = ” & customerID
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
If Not rs.EOF Then
‘ — 楽観的排他制御を明示的に有効化 —
rs.LockEdits = False
‘ Edit時点ではロックがかかりません(他の人も自由にいじれる)
rs.Edit
rs!電話番号 = newPhone
‘ 【重要】ここで「読み込み時とDBの状態が変わっていないか」がチェックされます
rs.Update
MsgBox “電話番号を更新しました!”, vbInformation, “完了”
End If
ExitHandler:
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
Set db = Nothing
Exit Sub
ErrHandler:
Select Case Err.Number
Case 3197 ‘ 読み込み後に他のユーザーがレコードを変更した場合
Dim response As VbMsgBoxResult
response = MsgBox(“あなたが編集している間に、他のユーザーが最新データを更新しました。” & vbCrLf & _
“あなたの入力内容で強制的に上書きしますか?”, _
vbQuestion + vbYesNo, “書き込み競合”)
If response = vbYes Then
‘ データを最新化(リフレッシュ)してから再実行するなどの救済措置
rs.DataUpToDate ‘ データの整合性を再確認
rs.Edit
rs!電話番号 = newPhone
rs.Update
MsgBox “最新データとして上書き保存しました。”, vbInformation
Else
rs.CancelUpdate ‘ 変更を破棄
MsgBox “更新をキャンセルしました。”, vbExclamation
End If
Case Else
MsgBox “予期せぬエラーが発生しました(” & Err.Number & “): ” & Err.Description, vbCritical
End Select
Resume ExitHandler
End Sub
—
5. どちらを使うべき?実務での「選定基準」
「概念は分かったけれど、自分のシステムではどちらを使えばいいの?」と迷いますよね。チーフアーキテクトとしての実践的な選び方の基準をお伝えします。
【判断のフローチャート】
[ Q1. 編集作業に時間がかかるか? ]
(例:メモ欄の長文入力、熟考する作業)
│
┌──────────┴──────────┐
YES NO
│ │
▼ ▼
[ 楽観的排他制御 ] [ Q2. 絶対に競合を失敗させられないか? ]
(他の人を待たせない) (例:銀行口座残高、在庫の引き落とし)
│
┌─────────┴─────────┐
YES NO
│ │
▼ ▼
[ 悲観的排他制御 ] [ 楽観的排他制御 ]
(厳格にロック) (パフォーマンス優先)
まとめ比較表
| 評価項目 | 悲観的排他制御 (`LockEdits = True`) | 楽観的排他制御 (`LockEdits = False`) |
| :— | :— | :— |
| ロックのタイミング | `.Edit` を呼んだ瞬間 | `.Update` を呼んだ一瞬 |
| 競合時のエラー | エラー `3260` (Edit時) | エラー `3197` (Update時) |
| システム性能(レスポンス) | 低め(ロックのオーバーヘッド大) | 高い(データベースへの負担が少ない) |
| 向いている業務 | 金融・在庫引き落とし・一括バッチ処理など | 顧客マスターの修正・一般的なデータ入力フォーム |
現代の業務システム(特にWebシステムやAPI開発)では、システム全体のパフォーマンスを高めるために「楽観的排他制御」が主流となっています。
しかし、Accessのようなローカルネットワーク(LAN)で動作するシステムで「何が何でもデータの矛盾を防ぎたい!」という場合は、悲観的排他制御も非常に強力な武器になります。
—
6. 一歩差がつく!現場のプロが実践する注意点
最後に、初心者から中級者へステップアップするための「ハマりやすい罠とベストプラクティス」を2つ紹介します。
① オブジェクトの解放(Close / Set = Nothing)を絶対に忘れない
`.Edit` や `LockEdits` でどんなに素晴らしい制御を書いても、エラーで処理が途中で落ち、`Recordset` や `Database` オブジェクトが開いたまま(メモリ上に残ったまま)になると、「謎の不滅のロック」が残り続けてしまいます。
紹介したコードのように、`ExitHandler:`(後始末用のラベル)を用意し、正常終了時もエラー発生時も、必ず `rs.Close` と `Set rs = Nothing` が実行される構造にしてください。
② Accessオプションの「レコードレベルのロック」をチェックする
昔のAccessは「ページレベルのロック(2KB単位でのロック)」という仕様で、目的の1行だけでなく、その隣にある関係ないレコードまで一緒にロックされてしまうという挙動をしていました。
これを防ぐために、Accessのオプション設定をあらかじめ確認しておきましょう。
1. Accessの `[ファイル]` > `[オプション]` を開く。
2. `[クライアントの設定]` を選択。
3. `[詳細設定]` セクションにある 「レコードレベルのロックを使用してデータベースを開く」 にチェックが入っていることを確認する。
これにチェックを入れておけば、より細やかで正確なレコードロックが実現します。
—
おわりに:これでAccess VBAの排他制御はマスター!
今回は、`DAO.Recordset` の `LockEdits` プロパティを使った「悲観的排他制御」と「楽観的排他制御」の極意をお伝えしました。
- `LockEdits = True`: 先手必勝でガッチリ鍵をかける「悲観的制御」
- `LockEdits = False`: 最後の保存時だけ確認するスマートな「楽観的制御」
この2つの違いを理解し、業務の重要度やユーザーの動きに合わせて使い分けられるようになれば、あなたの作るAccessアプリの信頼性は格段に跳ね上がります!
「今までなんとなく使っていたVBA」から「マルチユーザーの挙動まで見据えた本物のVBA」へ。ぜひ今回のコードを自分の環境で動かしてみて、その挙動を体感してみてくださいね。
応援しています!ここをクリアできれば、Access VBAの基礎・応用はもうバッチリですよ!
