【テクニカル・上級編】DAOのトランザクションで「ロック待ち」が発生した際の強制タイムアウトとリトライ処理の実装 – Access VBA解析バイブル

スポンサーリンク
スポンサーリンク

競合を制する者、システムを制す:DAOトランザクションにおけるロック待ち回避と堅牢なリトライ・アーキテクチャ

マルチユーザー環境におけるAccessアプリケーションの開発において、最もエンジニアの精神を削る事象は何か?それはバグではない。「不定期に発生するロック競合による、ユーザーの操作停止」である。

単一ユーザーでのテストでは完璧に動作するコードが、実稼働環境で10人が同時に操作した瞬間に「実行時エラー 3260: 更新できません。現在ロックされています」を吐いて死ぬ。これは、アプリケーションの設計思想が「シーケンシャルな処理」に留まり、「並行性の制御」を甘く見ている証左である。

本稿では、DAO(Data Access Objects)を用いたトランザクション処理において、ロック待ちを検知し、指数バックオフ(Exponential Backoff)を用いたリトライ戦略を組み込むことで、極限まで堅牢性を高める実装手法を解説する。これは単なるエラーハンドリングではない。システムの生存戦略である。

1. DAOにおけるロックの正体とアーキテクチャの罠

ADO(ActiveX Data Objects)が汎用的なデータ接続を目的としているのに対し、DAOはJet/ACEエンジンに最適化された、いわば「ネイティブな操作」を提供する。この最適化こそが、高パフォーマンスを実現する一方で、ロック制御の難度を上げている。

Accessのマルチユーザー環境では、`.laccdb`ファイルを通じてレコードレベルまたはページレベルのロックが管理される。トランザクション(`BeginTrans`)を開始した瞬間、エンジンの整合性を守るためにロックの強度は増す。

ここで初心者が犯す致命的なミスは、「エラーが出たらRollbackして諦める」、あるいは「無限ループで待ち続ける」のどちらかである。前者はデータの整合性を破壊し、後者はユーザーのUIをフリーズさせる。我々が目指すべきは、「適切な間隔を空けて、スマートに再試行する」ことだ。

2. Windows APIを用いた精密な待機制御

VBA標準の`Pause`的な手法(`DoEvents`を回すなど)は、CPUリソースを無駄に消費し、スレッドの制御も不安定である。プロフェッショナルの実装には、Windows APIの`Sleep`を用いる。これにより、OSレベルでスレッドを待機状態に移行させ、システムへの負荷を最小限に抑えつつ、ミリ秒単位の精密な制御が可能になる。

3. 実装:堅牢なリトライ・トランザクション・パターン

以下に示すのは、ロック競合(Error 3260等)を検知した際に、自動的にリトライを行う実装例である。ここでは、単なる固定秒数の待機ではなく、リトライごとに待機時間を増やす「指数バックオフ」を採用している。これにより、競合が激しい状況下での「衝突の連鎖(Thundering Herd問題)」を防ぐ。

Option Compare Database
Option Explicit

' --- Windows API Declaration ---

If VBA7 Then

' 64bit環境用 Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr) As Long

Else

' 32bit環境用 Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) As Long

End If

' --- 定数定義 --- Private Const MAX_RETRIES As Integer = 5 ' 最大リトライ回数 Private Const BASE_DELAY_MS As Long = 200 ' 初回待機時間(ms) Private Const ERR_LOCK_CONFLICT As Long = 3260 ' レコードがロックされている Private Const ERR_WRITE_CONFLICT As Long = 3211 ' 書き込み競合 ''' <summary> ''' ロック競合を考慮したトランザクション実行エンジン ''' </summary> Public Sub ExecuteSecureTransaction() Dim db As DAO.Database Dim ws As DAO.Workspace Dim retryCount As Integer Dim success As Boolean Dim currentDelay As Long Set db = CurrentDb Set ws = DBEngine.Workspaces(0) retryCount = 0 success = False currentDelay = BASE_DELAY_MS ' リトライループの開始 Do While retryCount <= MAX_RETRIES And Not success On Error GoTo ErrorHandler ' トランザクション開始 ws.BeginTrans ' --- 【実務ロジック領域】ここから業務処理を開始 --- Call PerformBusinessLogic(db) ' --- 【実務ロジック領域】ここまで --- ' 全て成功したらコミット ws.CommitTrans success = True Debug.Print "Transaction committed successfully." Loop If Not success Then MsgBox "処理に失敗しました。サーバーの負荷が高いか、他のユーザーが長時間ロックしています。", vbCritical End If CleanUp: ' オブジェクトの明示的な解放(メモリリークと残留ロック防止の鉄則) On Error Resume Next Set db = Nothing Set ws = Nothing Exit Sub ErrorHandler: ' ロック競合関連のエラーか判定 If Err.Number = ERR_LOCK_CONFLICT Or Err.Number = ERR_WRITE_CONFLICT Then ' トランザクションをロールバックして状態をリセット ws.Rollback retryCount = retryCount + 1 If retryCount <= MAX_RETRIES Then Debug.Print "Lock conflict detected (Error " & Err.Number & "). Retry " & retryCount & "/" & MAX_RETRIES & " after " & currentDelay & "ms." ' 指数バックオフによる待機 Sleep currentDelay currentDelay = currentDelay * 2 ' 待機時間を倍増させる ' エラーをクリアしてループ継続 Err.Clear Resume Do Else Debug.Print "Max retries reached. Abandoning transaction." GoTo CleanUp End If Else ' ロック以外の致命的なエラーは即座にロールバックして終了 ws.Rollback MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical GoTo CleanUp End If End Sub ''' <summary> ''' 実際のデータ更新処理(サンプル) ''' </summary> Private Sub PerformBusinessLogic(ByRef db As DAO.Database) Dim rs As DAO.Recordset ' 例: 在庫テーブルの更新 Set rs = db.OpenRecordset("SELECT * FROM T_Inventory WHERE ProductID = 1", dbOpenDynaset, dbOptimistic) If Not rs.EOF Then rs.Edit rs.Fields("StockCount").Value = rs.Fields("StockCount").Value - 1 rs.Update End If ' Recordsetを即座に閉じる。これが不十分だとロックが残る原因になる。 rs.Close Set rs = Nothing End Sub

4. 伝説のアーキテクトが教える「極限の知見」

コードを書いただけでは不十分だ。以下の3点を血肉にせよ。

① オブジェクトのライフサイクル管理

`PerformBusinessLogic` 内で `Recordset` を開いた後、`rs.Close` および `Set rs = Nothing` を徹底しているか? トランザクション中にオブジェクトがメモリ上に浮遊していると、`Rollback` を実行してもエンジンが「まだ使用中」と判断し、ロックが解放されないケースがある。「使い終わったら即座に殺す」。これはVBAにおける生存本能である。

② 冪等性(Idempotency)の確保

リトライ処理を導入する場合、`PerformBusinessLogic` 内の処理は冪等(べきとう)でなければならない。つまり、「何度実行しても、結果が同じになる」ことだ。もし、関数の途中で外部システムへメールを送信するような処理が含まれていたら、リトライのたびにメールが飛んでしまう。トランザクションの範囲内には、「データの整合性に影響を与える純粋なデータ操作」のみを封じ込めよ。

③ トランザクションの粒度

トランザクションを長く保持すればするほど、ロック競合の確率は指数関数的に上昇する。
「全工程を一つのトランザクションにする」という安易な設計は、大規模環境では死を意味する。可能な限り、更新単位を最小限に分割し、一瞬でコミットを完了させる設計を追求せよ。

結論

Access VBAにおける堅牢なシステムとは、エラーが起きないシステムではない。エラーが起きた際、システムがいかに自律的に、かつユーザーにストレスを与えずに復旧できるか、その設計にこそエンジニアの真価が問われる。

今回提示したリトライ・アーキテクチャを基盤に、君たちのシステムを「止まらない、壊れない、妥協のない」ものへと昇華させてほしい。

タイトルとURLをコピーしました