「レコードがロックされています」を克服せよ。Access VBAで極める、堅牢なトランザクションとリトライ処理の極意
こんにちは。現場の最前線で、数々の「動かないシステム」を「止まらないシステム」へと作り替えてきたアーキテクトです。
Access VBAを使いこなせるようになってくると、必ずと言っていいほど直面する壁があります。それが「マルチユーザー環境でのロック競合」です。
「開発機では完璧に動いていたのに、いざ運用を始めたら、他のユーザーがデータを開いている時にエラーで止まってしまう……」
もしあなたが今、そんな経験をして立ち止まっているなら、安心してください。この記事を読み終える頃には、あなたは単なる「マクロの延長」ではなく、「エラーを予測し、自律的に回復するプロフェッショナルなコード」を書けるようになっているはずです。
さあ、一歩先のエンジニアリングの世界へ踏み出しましょう。
1. 基礎知識:DAOとADO、どちらの「武器」を使うべきか?
本題に入る前に、アーキテクチャの根幹となる「DAO」と「ADO」の違いを整理しておきましょう。ここを曖昧にしていると、パフォーマンスの最適化で必ず躓きます。
| 特徴 | DAO (Data Access Objects) | ADO (ActiveX Data Objects) |
|---|---|---|
| 主な用途 | Access内部のテーブル操作に最適 | SQL Serverなどの外部DBへの接続に最適 |
| 速度 | Accessエンジン(ACE/Jet)と密結合しており高速 | 通信プロトコルを介するため、Access単体ではやや重い |
| トランザクション | Access特有の「ワークスペース」管理が強力 | データベース接続(Connection)単位で管理 |
「Access内のテーブルを操作するなら、迷わずDAOを選べ」
これが、設計における鉄則です。DAOはAccessのエンジンと直接対話するため、リレーションシップやインデックスを最大限に活かした高速な操作が可能です。
2. なぜ「ロック待ち」は発生し、システムを壊すのか?
マルチユーザー環境では、あるユーザーがレコードを編集している間、そのレコードには「鍵(ロック)」がかかります。
別のユーザーが同じレコードを更新しようとすると、Accessは「今、他の人が使っていますよ!」とエラーを返します。これがロック競合(Lock Contention)です。
一般的な初級者のコードは、エラーが出た瞬間に「エラー終了」してしまいます。
' 典型的な「止まってしまう」コード
On Error GoTo ErrorHandler
DoCmd.RunSQL "UPDATE T_在庫 SET 在庫数 = 在庫数 - 1 WHERE 商品ID = 1"
Exit Sub
ErrorHandler:
MsgBox "エラーが発生しました: " & Err.Description ' ここで処理が止まり、ユーザーは作業を中断せざるを得ない
これでは、ユーザーの作業体験(UX)は最悪です。プロのエンジニアは、「エラーが起きたら、少し待ってから自動でやり直す(リトライ)」という設計を組み込みます。
3. 【実践】リトライ処理を組み込んだ堅牢なトランザクション実装
では、実際に「ロックが発生しても、指定回数内なら自動でリトライし、それでもダメなら適切に終了する」という、極めて実戦的なコードを見ていきましょう。
ここでは、「トランザクション(一連の処理をひとまとめにし、失敗したら全てなかったことにする仕組み)」と「リトライ・ループ」を組み合わせて実装します。
Option Compare Database
Option Explicit
' Windows APIの宣言: プログラムを一時停止(スリープ)させるために使用
' これにより、CPUに負荷をかけずに「待ち」を作ることができます
If VBA7 Then
Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr)
Else
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
End If
''' <summary>
''' 在庫更新処理(リトライ機能付きトランザクション)
''' </summary>
Public Sub SafeUpdateStock(ByVal targetID As Long, ByVal changeAmount As Integer)
Dim db As DAO.Database
Dim ws As DAO.Workspace
Dim rs As DAO.Recordset
Dim retryCount As Integer
Const MAX_RETRIES As Integer = 5 ' 最大リトライ回数
Const RETRY_INTERVAL As Long = 1000 ' リトライ間の待機時間 (ミリ秒)
Dim isSuccess As Boolean
isSuccess = False
Set ws = DBEngine.Workspaces(0)
Set db = CurrentDb
' --- リトライループの開始 ---
Do While retryCount < MAX_RETRIES And isSuccess = False
On Error GoTo TransactionError
' 1. トランザクション開始(ここから一連の処理が「ひとまとめ」になる)
ws.BeginTrans
' 2. レコードの取得と更新
Set rs = db.OpenRecordset("SELECT 在庫数 FROM T_在庫 WHERE 商品ID = " & targetID, dbOpenDynaset)
If Not rs.EOF Then
rs.Edit
rs.Fields("在庫数").Value = rs.Fields("在庫数").Value + changeAmount
rs.Update
' 3. 全て成功したらコミット(確定)
ws.CommitTrans
isSuccess = True
Debug.Print "成功: 更新が確定しました。"
Else
' 対象レコードがない場合はロールバックして終了
ws.Rollback
MsgBox "対象の商品が見つかりませんでした。", vbExclamation
Exit Sub
End If
rs.Close
Set rs = Nothing
' ループを抜ける(成功時)
Exit Do
TransactionError:
' エラーが発生した場合の処理
' エラー番号 3260: 別のユーザーによってロックされている
' エラー番号 3167: ロック競合
If Err.Number = 3260 Or Err.Number = 3167 Then
retryCount = retryCount + 1
' 失敗したトランザクションをロールバック(中途半端な状態を防ぐ)
ws.Rollback
Debug.Print "ロック競合検知。リトライ中... (" & retryCount & "/" & MAX_RETRIES & ")"
' リソースを解放
If Not rs Is Nothing Then rs.Close: Set rs = Nothing
' 指定時間待機してから再試行
Sleep RETRY_INTERVAL
Else
' ロック以外の致命的なエラー(型違い、構文エラー等)は即座に中断
ws.Rollback
MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
Exit Sub
End If
Loop
' --- ループ終了後の判定 ---
If Not isSuccess Then
MsgBox "サーバーが混み合っているため、処理を完了できませんでした。時間を置いて再度お試しください。", vbExclamation
End If
Set rs = Nothing
Set db = Nothing
Set ws = Nothing
End Sub
4. アーキテクトの視点:コードの「急所」を解説
このコードがなぜ「プロフェッショナル」なのか。その理由は3つの設計思想にあります。
① `BeginTrans` と `Rollback` の徹底
トランザクションの最大の目的は「データの整合性」です。
もし、在庫を減らした後に、別の「売上履歴テーブルへの書き込み」でエラーが起きたらどうなるでしょうか? 在庫だけ減って、売上履歴がない……という「データの矛盾」が発生します。
`ws.Rollback` を適切に呼ぶことで、エラーが起きた瞬間に「すべてを処理前の状態に戻す」ことができるのです。
② `Sleep` API による「賢い待ち」
`DoEvents` を使って空ループで待機するのは、CPUに過剰な負荷をかけるため、避けるべきです。Windows APIの `Sleep` を使うことで、OSに対して「このプログラムは今、休んでいます」と伝え、システム全体のパフォーマンス低下を防ぎます。
③ エラー番号の選別
すべてのエラーに対してリトライを行うのは愚策です。
- リトライすべきもの: ロック競合(3260, 3167など)。これは「時間が解決する」問題です。
- リトライしてはいけないもの: 文法エラーや型違い。これらは何度繰り返しても同じ結果になります。
このように、「リトライの価値があるエラー」を厳選している点が、この設計の肝です。
まとめ:ここをクリアすれば、Access VBAの基本はバッチリですよ
いかがでしたでしょうか?
1. DAOを使い、高速なデータ操作を行う。
2. トランザクションでデータの整合性を守る。
3. ロック競合には「リトライ」と「スリープ」で賢く対処する。
この3点をマスターすれば、あなたの作るAccessアプリケーションは、現場のユーザーから「このシステム、すごく安定しているね!」と信頼されるものに変わります。
最初は難しく感じるかもしれませんが、コードを写して、エラーをわざと起こして、挙動を確認してみてください。その繰り返しが、あなたを伝説のエンジニアへと導きます。
応援していますよ。次はもっと高度な、API連携や外部DBとのハイブリッド構成に挑戦してみましょう!
