Access VBAを掌握する極限の知見:DAOのトランザクションで「ロック待ち」を回避する設計思想
こんにちは。Access VBAの世界へようこそ。
マクロの記録から一歩踏み出し、「自分でコードを書いてデータを操作する」という領域に足を踏み入れたあなたは、エンジニアとしての第一歩を力強く踏み出しています。
さて、データ操作ができるようになると、次に直面するのが「データの整合性」という壁です。
「複数のテーブルを同時に更新したいけれど、途中でエラーが起きたら片方だけ更新されてデータがめちゃくちゃになる……」
そんな恐怖を打ち消してくれる救世主が「トランザクション」です。
しかし、この強力な武器には、扱いを誤るとアプリケーションをフリーズさせ、他のユーザーを怒らせる「諸刃の剣」としての側面があります。今回は、多くの開発者が陥る「ロック待ち(デッドロック)」の正体を暴き、プロフェッショナルが実践する「正しいトランザクション設計」を伝授しましょう。
ここをクリアすれば、あなたのAccessアプリケーションの信頼性は、一段上のステージへと引き上げられますよ。
1. トランザクションとは「全か無か」の約束事
まず、基本をおさらいしましょう。
トランザクションとは、「複数の処理を一つのグループとしてまとめ、すべて成功するか、すべて失敗(なかったことに)するかを保証する仕組み」です。
例えば、銀行の振込処理をイメージしてください。
1. Aさんの口座から1万円引く
2. Bさんの口座に1万円足す
もし「1」が成功して「2」でエラーが起きたら、1万円が消えてしまいますよね? これを防ぐために、`BeginTrans`(開始)から `CommitTrans`(確定)までの間にエラーが起きたら、`Rollback`(取り消し)を行って、「振込前の状態に丸ごと戻す」。これがトランザクションの本質です。
2. なぜ「ロック待ち」が発生するのか?
トランザクションを開始すると、Access(DAO)はデータの整合性を守るために、「今からこのデータを書き換えるから、他の人は触らないで!」という予約をかけます。これが「ロック(排他制御)」です。
ここで問題が発生します。
「トランザクションの範囲(BeginTransからCommitTransまで)が長すぎる」と、その間ずっとロックがかかり続けてしまうのです。
恐ろしい「待ち」のシナリオ
1. ユーザーAがトランザクションを開始し、顧客テーブルのレコードを更新。
2. ユーザーAのコードの中に、なぜか「処理が終わりました。よろしいですか? [OK]」というメッセージボックスが入っている。
3. ユーザーAがコーヒーを飲みに行き、メッセージボックスを放置。
4. その間、ユーザーBが同じ顧客テーブルを更新しようとする。
5. ユーザーBのAccessは、「Aさんが作業中なので、終わるまで待ちます……」とフリーズ(ロック待ち)状態になる。
これが、現場で最も発生しやすい「ロック待ち」の典型例です。
図解:ロックのライフサイクル
[ 良い設計 ]
BeginTrans → [高速なデータ更新のみ] → CommitTrans
(ロック時間はコンマ数秒。他のユーザーへの影響はほぼゼロ)
[ 悪い設計 ]
BeginTrans → [重い計算] → [ユーザーへの確認(MsgBox)] → [ネットワーク通信] → CommitTrans
(ロック時間が数秒〜数分。他のユーザーは全員ストップ!)
3. デッドロックを回避する「極限の設計パターン」
プロフェッショナルなエンジニアは、トランザクションの範囲を「極限まで小さく」設計します。
以下の3つの黄金律を覚えておいてください。
黄金律1:準備は「外」で、実行は「中」で
複雑な計算や、ユーザーへの入力確認、外部ファイルとの照合などは、すべて `BeginTrans` を呼ぶ前に終わらせておきます。トランザクションの中には、「確定させるべき純粋なデータ更新処理」だけを詰め込むのです。
黄金律2:メッセージボックスを「中」に入れない
トランザクション中にユーザーの操作を待つことは、絶対に避けてください。ユーザーが「キャンセル」を押したとき、ロールバック処理を複雑にする原因にもなります。
黄金律3:エラーハンドリングを完璧にする
もしエラーが起きて `Rollback` が実行されなかった場合、ロックが解除されず、データベースが「永遠にロックされたまま」になる最悪の事態を招きます。必ず `On Error GoTo` を使い、エラー時には確実にロールバックされる構造にします。
4. 実践:正しいトランザクションの書き方
それでは、実際に「悪い例」と「良い例」のコードを比較してみましょう。
❌ 失敗するコード(ロック待ちを引き起こす)
' 【警告】これはプロの現場ではNGなコードです
Sub BadTransactionExample()
Dim db As DAO.Database
Dim ws As DAO.Workspace
Set db = CurrentDb
Set ws = DBEngine.Workspaces(0)
On Error GoTo Err_Handler
ws.BeginTrans ' トランザクション開始
' 1. データ更新
db.Execute "UPDATE T_Inventory SET Stock = Stock - 1 WHERE ID = 1", dbFailOnError
' --- ここが致命的な問題 ---
' ユーザーの入力を待ってしまうため、ロックが長時間続く!
If MsgBox("在庫を更新しました。続けて他の処理を行いますか?", vbYesNo) = vbNo Then
ws.Rollback
Exit Sub
End If
' --------------------------
ws.CommitTrans ' 確定
MsgBox "完了しました。"
Exit Sub
Err_Handler:
ws.Rollback ' エラー時は戻す
MsgBox "エラーが発生しました: " & Err.Description
End Sub
✅ 成功するコード(プロフェッショナルな設計)
' 【推奨】ロックを最小限に抑えた、安全で高速な設計
Sub GoodTransactionExample()
Dim db As DAO.Database
Dim ws As DAO.Workspace
Dim isConfirmed As VbMsgBoxResult
' --- 1. 事前準備フェーズ (トランザクションの外) ---
' ユーザーへの確認や、重い計算はすべてここで行う
isConfirmed = MsgBox("在庫を更新しますか?", vbYesNo + vbQuestion)
If isConfirmed = vbNo Then Exit Sub
' データベースオブジェクトのセットアップ
Set db = CurrentDb
Set ws = DBEngine.Workspaces(0)
' --- 2. 実行フェーズ (トランザクションの中は「最短」にする) ---
On Error GoTo Err_Handler
ws.BeginTrans ' ここでロック開始!
' 高速なSQL実行のみを行う
db.Execute "UPDATE T_Inventory SET Stock = Stock - 1 WHERE ID = 1", dbFailOnError
' 完了したら即座にコミット!
ws.CommitTrans ' ここでロック解除!
MsgBox "在庫を更新しました。", vbInformation
Exit_Handler:
' オブジェクトの解放(メモリ管理もエンジニアの嗜み)
Set db = Nothing
Set ws = Nothing
Exit Sub
Err_Handler:
' エラーが発生した場合は、確実にロールバックしてロックを解放する
If Not ws Is Nothing Then
ws.Rollback
End If
MsgBox "エラーが発生したため、処理を取り消しました。" & vbCrLf & _
"エラー内容: " & Err.Description, vbCritical
Resume Exit_Handler
End Sub
まとめ:エンジニアとしての視点
いかがでしたか?
トランザクションは非常に便利な機能ですが、その裏側では「他のユーザーの作業を止めてしまうかもしれない」というリスクが常に潜んでいます。
- トランザクションの範囲を最小限にする。
- 「計算」「確認」「通信」はトランザクションの外で行う。
- エラー発生時は確実に `Rollback` する。
この原則を守るだけで、あなたの作るシステムは、数百人が同時に使うような大規模な環境でも耐えうる、強固なものへと進化します。
「動けばいい」コードから、「誰がいつ使っても安全な」コードへ。
その一歩が、あなたを真のエンジニアへと導いてくれます。応援していますよ!
