【テクニカル・上級編】DAOのRecordsetを「配列」に一括変換して処理する:ループ処理の速度を10倍にするテクニック – Access VBA解析バイブル

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

Recordsetの「ループ」を捨てよ:GetRowsによるメモリ局所性の極致と、DAOアーキテクチャの真実

多くの開発者が、Access VBAにおけるデータ処理を「Recordsetを1行ずつ `MoveNext` で回すこと」だと誤解している。

もし君が、数万件のレコードを扱うシステムにおいて、`Do Until rs.EOF` という構文を漫然と使い続けているのなら、君はエンジニアとして致命的な損失を招いている。それは単なる「書き方の問題」ではない。データエンジンとアプリケーション間の通信オーバーヘッド、およびメモリ局所性(Memory Locality)を完全に無視した、設計上の敗北である。

本稿では、DAOのRecordsetを「配列」へと一括変換し、処理速度を桁違いに向上させる `GetRows` メソッドの真髄を解説する。これは、レガシーな環境を現代的なパフォーマンス水準へと引き上げるための、極めて実戦的なアーキテクチャ戦略である。

1. なぜ `MoveNext` は遅いのか:カーソル移動の物理的コスト

なぜ `Do Until rs.EOF` は遅いのか。その理由は、Recordsetが「データそのもの」ではなく、データベースエンジン(ACE/Jet)への「窓口(インターフェース)」に過ぎないからだ。

`MoveNext` を実行するたびに、VBAの実行エンジンは以下のプロセスを踏む:
1. DAOオブジェクトを介して、データベースエンジンへ次行のポインタ移動を要求する。
2. エンジン側でカーソル位置を更新し、要求されたフィールドのメタデータをメモリ上にロードする。
3. VBAのVariant型へとデータをブリッジ(型変換)する。

この「オブジェクトを介した往復」が、ループの回数分だけ繰り返される。10万回のループがあれば、10万回のコンテキストスイッチに近いオーバーヘッドが発生するのだ。我々が目指すべきは、この「窓口への問い合わせ」を最小化し、データを一気にVBAのメモリ空間(ヒープ領域)へ引き込むことである。

2. `GetRows` によるバルク・フェッチ戦略

ここで登場するのが `GetRows` メソッドである。このメソッドは、Recordsetが保持する全レコード(または指定した件数)を、一度の呼び出しで Variant型の2次元配列 に一括転送する。

これにより、データベースエンジンとの通信は「1回」で完結する。データはVBAのメモリ上に展開され、以降の処理は低レイヤーのメモリ操作に近い速度で行われる。これが、ループ処理を10倍、あるいはそれ以上に高速化させる理論的根拠である。

3. 実装例:速度差を可視化する

以下のコードは、標準的な `MoveNext` による処理と、`GetRows` を用いた配列処理のパフォーマンスを比較するためのベンチマークである。

Option Explicit

' ======================================================================================
' 性能検証用モジュール
' 目的: Recordsetの逐次走査とGetRowsによる一括取得の速度差を測定する
' ======================================================================================

Public Sub Benchmark_RecordsetProcessing()
    Dim db As DAO.Database
    Dim rs As DAO.Recordset
    Dim i As Long
    Dim startTime As Double
    Dim totalRows As Long
    
    ' テストデータの準備(事前に大量のレコードを持つテーブルを用意しておくこと)
    Set db = CurrentDb
    Set rs = db.OpenRecordset("SELECT ID, TargetValue FROM tblLargeData", dbOpenSnapshot)
    
    If rs.EOF Then
        MsgBox "データが存在しません。"
        Exit Sub
    End If
    
    rs.MoveLast
    totalRows = rs.RecordCount
    rs.MoveFirst
    
    Debug.Print "--- Benchmark Start: " & totalRows & " rows ---"

    ' --- パターン1: 従来の逐次処理 (MoveNext) ---
    startTime = Timer
    Dim sum1 As Double
    sum1 = 0
    Do Until rs.EOF
        sum1 = sum1 + rs!TargetValue
        rs.MoveNext
    Loop
    Debug.Print "Pattern 1 (MoveNext): " & Format(Timer - startTime, "0.000") & " sec (Sum: " & sum1 & ")"
    
    ' --- パターン2: GetRows による配列処理 ---
    rs.MoveFirst
    startTime = Timer
    
    ' Recordsetの全データを一括で2次元配列に格納
    Dim dataArray As Variant
    dataArray = rs.GetRows
    
    Dim sum2 As Double
    sum2 = 0
    
    ' GetRowsの特性: 配列のインデックスは [Column, Row] になることに注意
    ' 0番目の列(ID)を飛ばして、1番目の列(TargetValue)を走査
    Dim colIdx As Long: colIdx = 1
    Dim rowIdx As Long
    
    ' LBound/UBoundを使用して配列の境界を厳密に制御
    For rowIdx = LBound(dataArray, 2) To UBound(dataArray, 2)
        sum2 = sum2 + dataArray(colIdx, rowIdx)
    Next rowIdx
    
    Debug.Print "Pattern 2 (GetRows): " & Format(Timer - startTime, "0.000") & " sec (Sum: " & sum2 & ")"
    
    ' --- 後処理 ---
    ' オブジェクトのライフサイクルを明示的に管理
    rs.Close
    Set rs = Nothing
    Set db = Nothing
    
    Debug.Print "--- Benchmark End ---"
End Sub

4. 極限の知見:`GetRows` の「罠」とメモリ管理

このテクニックをマスターするためには、二つの重要な知見を脳に刻んでおく必要がある。

① 配列の次元構造(Transposed Matrix)

`GetRows` が返す配列は、一般的な感覚とは異なる構造をしている。
通常、我々は `Array(Row, Column)` という構造を期待するが、`GetRows` が生成するのは `Array(Column, Row)` である。

  • `dataArray(0, 5)` は、「0番目のフィールドの、6行目のデータ」を指す。

この構造を理解せずに `dataArray(row, col)` とアクセスすれば、即座に `Subscript out of range` エラーでシステムは沈黙する。この「転置された構造」こそが、メモリ上の連続性を確保し、高速な列アクセスを可能にする設計思想なのだ。

② オブジェクトのライフサイクルとメモリ解放

大規模なデータを `GetRows` で取得する場合、一時的にメモリ使用量が急増する。VBAは強力なガベージコレクションを持たないため、使い終わった `Recordset` オブジェクトや、巨大な `Variant` 配列は、明示的に `Set … = Nothing` もしくは `Erase` を行い、スコープから外すことが鉄則である。

特に、ループ内で `GetRows` を繰り返し呼び出すような設計は、メモリリーク(厳密にはメモリの断片化と枯渇)を引き起こし、Windows OS全体の不安定化を招くリスクがある。

結論:アーキテクトとしての選択

「動けばいい」というレベルのプログラマは、`MoveNext` を使い続ける。
しかし、システムの堅牢性と極限のパフォーマンスを追求するアーキテクトは、データの流れ(Data Flow)を制御し、最小のコストで最大の出力を得る。

`GetRows` によるバルク・フェッチは、Access VBAというレガシーな武器を、現代のデータ処理エンジンへと昇華させるための、最も基本的かつ強力な戦術である。君のコードに、この「集合論的思考」を取り入れてほしい。

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