【実務・中級編】ADOのBatchUpdateモードで実現する:複数レコードの一括更新と競合解決の実践 – Access VBA解析バイブル

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

ネットワークの壁を突破せよ:ADO BatchUpdateによる高速一括更新と競合解決の極意

業務アプリケーションの開発において、多くのエンジニアが陥る「初歩的な罠」がある。それは、「ループの中でSQLを発行する」という設計だ。

1,000件のデータを更新するために、1,000回 `UPDATE` 文をネットワーク越しにSQL Serverへ投げる。この設計は、一見するとシンプルで理解しやすい。しかし、これはプロフェッショナルなアーキテクトが絶対に選ばない「最悪の選択」だ。ネットワークの往復(Round-trip)によるレイテンシが積み重なり、ユーザーは「フリーズした」と感じ、データベースサーバーは無駄な通信オーバーヘッドに悲鳴を上げる。

本稿では、このボトルネックを根底から破壊するADOの`BatchUpdate`モードについて、そのアーキテクチャ的背景から、実戦で必ず直面する「更新競合」の解決策まで、極限の知見を伝授する。

1. なぜ「DAO」ではなく「ADO」なのか:アーキテクチャの境界線

まず、基礎的な設計思想を整理しておく。Access開発において、DAO(Data Access Objects)とADO(ActiveX Data Objects)の使い分けは、単なる好みの問題ではない。

  • DAOは、Access(Jet/ACEエンジン)の内部構造に深く根ざした、ローカルファイル操作に最適化されたエンジンだ。
  • ADOは、SQL ServerやOracleといった、エンタープライズ層のRDBMSへ接続するための、汎用的かつ強力なブリッジだ。

大量のデータをSQL Serverへ流し込む、あるいは同期させる場合、我々が手にすべき武器はADOである。そして、その真価を発揮させる鍵が、レコードセットをクライアントサイドのメモリ上に展開する`CursorLocation = adUseClient`の設定にある。

2. BatchUpdateのメカニズム:通信回数の劇的な削減

通常の `Update` メソッドは、呼び出されるたびにサーバーへ命令を飛ばす。対して `UpdateBatch` は、変更内容をクライアント側のメモリ(レコードセット)に一旦蓄積し、一括してサーバーへパケットを送り込む。

この挙動を実現するためには、以下の3つの条件が不可欠である。

1. `CursorLocation` を `adUseClient` に設定すること:サーバー側カーソルでは、クライアント側での変更の「保持」ができない。
2. `BatchSize` を適切に設定すること:一度に送るチャンクのサイズを制御する。
3. トランザクションの範囲を設計すること:一括更新は「全成功か全失敗か」の原則に従う必要がある。

3. 実践:堅牢な一括更新プロトコル

以下に、実務でそのまま使える、プロダクションレベルのコードパターンを示す。単なるコピペで終わらせるのではなく、なぜこの構造が必要なのかを理解してほしい。

Option Explicit

''' <summary>
''' SQL Serverに対して大量のレコードを一括更新する。
''' 更新競合が発生した場合は、エラーコレクションを走査してログを記録する。
''' </summary>
Public Sub ExecuteHighPerformanceBatchUpdate()
    Dim cn As ADODB.Connection
    Dim rs As ADODB.Recordset
    Dim strConn As String
    Dim i As Long
    Dim errorCount As Long
    
    ' 1. 接続文字列の設定(環境に合わせて適切に管理すること)
    strConn = "Provider=SQLNCLI11;Data Source=YOUR_SERVER;Initial Catalog=YOUR_DB;Integrated Security=SSPI;"
    
    On Error GoTo ErrorHandler

    Set cn = New ADODB.Connection
    cn.ConnectionString = strConn
    cn.Open

    ' 2. Recordsetの構築
    ' BatchUpdateを使用するためには、必ず adUseClient を指定する
    Set rs = New ADODB.Recordset
    rs.CursorLocation = adUseClient
    
    ' 更新対象のデータを取得(ここでは例として全件取得)
    ' ※実務ではWHERE句で対象を絞り込むこと
    rs.Open "SELECT ID, Status, UpdateCount FROM TargetTable", cn, adOpenStatic, adLockOptimistic

    ' BatchSizeの設定(メモリ消費と速度のトレードオフ)
    rs.BatchSize = 100

    ' 3. メモリ上でのデータ操作(ここまではネットワーク通信が発生しない)
    If rs.EOF Then Exit Sub
    
    Do Until rs.EOF
        ' 何らかのビジネスロジックに基づいた更新
        rs!Status = "PROCESSED"
        rs!UpdateCount = rs!UpdateCount + 1
        
        ' レコードの変更をメモリにマークする
        rs.Update
        rs.MoveNext
    Loop

    ' 4. 変更内容の一括送信(ここで初めてネットワーク通信が発生する)
    Debug.Print "BatchUpdateを開始します..."
    rs.UpdateBatch
    Debug.Print "BatchUpdateが正常に完了しました。"

CleanUp:
    ' リソースの解放
    If Not rs Is Nothing Then
        If rs.State = adStateOpen Then rs.Close
    End If
    If Not cn Is Nothing Then
        If cn.State = adStateOpen Then cn.Close
    End If
    Set rs = Nothing
    Set cn = Nothing
    Exit Sub

ErrorHandler:
    ' 5. 競合解決(Optimistic Concurrency Errorのハンドリング)
    ' UpdateBatchが失敗した場合、エラーは rs.Errors コレクションに格納される
    If Err.Number = -2147217887 Then ' ADODB_E_ERRORS_OCCURRED (競合が発生した際の代表的なエラー)
        Debug.Print "警告: 更新競合が発生しました。個別のエラーを確認します。"
        
        Dim errObj As ADODB.Error
        errorCount = 0
        
        For Each errObj In rs.Errors
            errorCount = errorCount + 1
            ' ここで競合したレコードの特定や、ログ記録を行う
            ' errObj.Description には、どのレコードが、なぜ失敗したかの詳細が含まれる
            Debug.Print "Error " & errorCount & ": " & errObj.Description
        Next errObj
        
        ' 戦略的判断:
        ' 全件ロールバックするか、競合したものを除いて再試行するかを設計に基づいて決定する
        MsgBox errorCount & " 件の更新競合が発生しました。詳細はログを確認してください。", vbCritical
    Else
        MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
    End If
    Resume CleanUp
End Sub

4. アーキテクトの視点:競合解決(Concurrency Resolution)の設計

上記のコードで最も重要なのは、`ErrorHandler` 内での `rs.Errors` の扱いだ。

`BatchUpdate` を使用する場合、複数のレコードが同時に更新されていると、「楽観的並行性制御(Optimistic Concurrency Control)」に抵触する。これは、「自分がデータを読み取ってから、更新をかけるまでの間に、他の誰かがデータを書き換えた」場合に発生する。

設計者として、以下の3つの戦略のいずれかを事前に決めておく必要がある。

1. Fail Fast(即時停止): 1件でも競合があれば、全件をロールバックし、ユーザーに手動での再入力を促す。最も安全だが、ユーザー体験は低い。
2. Ignore & Log(無視と記録): 競合したレコードのみをスキップし、成功したものだけを確定させる。ログを詳細に残し、後で管理者がフォローする。
3. Last Win / Force Update(強制上書き): 競合を無視して、現在の値を強制的に書き込む。これはデータ整合性の観点から、極めて慎重な判断が求められる。

5. まとめ:堅牢なシステムへの道

Access VBAを単なる「小規模なツール作成の手段」と捉えるか、「エンタープライズ・データ連携のハブ」と捉えるか。その差は、こうした低レイヤの通信制御と、エラーに対する設計思想の深さに現れる。

  • `adUseClient` を使いこなせ。
  • `UpdateBatch` でネットワークの往復を最小化せよ。
  • `Errors` コレクションを軽視せず、競合解決のロジックを設計に組み込め。

これらを遵守することで、君が作るツールは、単なる「動くコード」から、過酷な業務現場に耐えうる「信頼されるシステム」へと昇華する。

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