【実務・中級編】ADOのCursorTypeプロパティが及ぼす影響:動的カーソルと静的カーソルの実務的使い分け – Access VBA解析バイブル

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

カーソルを制する者は、データベースを制する。ADO CursorTypeの深淵と実務的設計論

業務自動化の現場において、Access VBAは単なる「マクロ」ではない。それは、エンタープライズ級のデータ操作を支える強力なエンジンだ。

しかし、多くの開発者が「なぜか動作が重い」「特定の条件下でデータが更新されない」「同時実行時にエラーが出る」といった問題に直面し、その原因を「ネットワークのせい」や「PCのスペック不足」に転嫁してしまう。

断言しよう。その原因の多くは、君たちが無意識に選択しているADOの「CursorType(カーソルタイプ)」の理解不足にある。

本稿では、設計の根幹に関わる `CursorType` のメカニズムを解剖し、パフォーマンスと整合性を極限まで両立させるための、プロフェッショナルな判断基準を伝授する。

1. カーソルとは「データの写し鏡」である

データベースからレコードセットを取得する際、プログラムはデータそのものを直接触っているわけではない。メモリ上、あるいはサーバー上に展開された「カーソル」というポインタを通じてデータにアクセスしている。

このカーソルが「どのようにデータを保持し、他者の変更をどう検知するか」を決めるのが `CursorType` プロパティだ。ここを誤ると、システムは「鈍重な重戦車」になるか、「不安定なガラス細工」になるかのどちらかになる。

静的カーソル(Static Cursor)の本質

`adOpenStatic` を指定した場合、取得した瞬間のデータの「スナップショット」がメモリ上に構築される。

  • 挙動: 他のユーザーがレコードを追加・削除・更新しても、君のカーソルには一切反映されない。
  • 利点: 圧倒的な読み取り速度。データが動かないことが保証されるため、計算やレポート出力に最適。
  • 欠点: 「リアルタイム性」は皆無。

動的カーソル(Dynamic Cursor)の本質

`adOpenDynamic` を指定した場合、カーソルは常にデータソースの最新状態を追いかけようとする。

  • 挙動: 他のユーザーが行った追加、削除、更新が、君のレコードセットにもリアルタイムに反映される。
  • 利点: 常に「今」のデータが見える。
  • 欠点: 膨大なオーバーヘッド。カーソルを移動するたびに、データベースエンジンは「最新の状態か?」を確認しにいく。これがネットワーク負荷とCPU負荷を跳ね上げる。

2. アーキテクトの決断:どちらを選ぶべきか?

実務において、君は「エンジニア」である前に「設計者」であれ。すべての操作を「最新の状態が見える動的カーソル」にするのは、設計の敗北だ。

判断基準のメトリクス

要件 推奨されるCursorType 設計思想
一括集計・レポート出力 `adOpenForwardOnly` / `adOpenStatic` 「一貫性」を優先。 処理中にデータが変わると計算結果が狂うため、スナップショットが正義。
マスタ参照・ドロップダウン `adOpenStatic` 「速度」を優先。 マスタは頻繁に変わらない。高速な読み込みがUXに直結する。
複雑な業務入力画面 `adOpenKeyset` (中間) 「整合性」と「効率」の妥協点。 他者の更新は検知したいが、動的カーソルほどの負荷は避けたい場合。
リアルタイム監視・在庫管理 `adOpenDynamic` 「最新性」を優先。 速度を犠牲にしてでも、今この瞬間の数値が不可欠な領域に限定する。

3. プロダクション・コード:堅牢なデータアクセス・レイヤー

単に `Recordset.Open` を書くのではない。エラーハンドリング、オブジェクトの解放、そして明示的なプロパティ設定。これらが揃って初めて「製品レベル」のコードと言える。

以下に、実務でそのまま利用可能な、保守性の高い実装パターンを示す。

Option Explicit

' ======================================================================================
' Module: modDataAccess
' Description: 堅牢なデータアクセスを実現するための標準モジュール
' Author: Chief Architect
' ======================================================================================

Public Enum DataAccessMode
    Mode_ReadOnly_Fast = 0    ' 高速読み取り用 (Static/ForwardOnly)
    Mode_RealTime_Live = 1    ' リアルタイム更新用 (Dynamic)
End Enum

''' <summary>
''' 指定されたモードに基づき、最適化されたRecordsetを取得する
''' </summary>
''' <param name="sql">実行するSQL文</param>
''' <param name="mode">DataAccessMode (高速かリアルタイムか)</param>
''' <returns>開いた状態のADODB.Recordsetオブジェクト</returns>
Public Function GetOptimizedRecordset(ByVal sql As String, ByVal mode As DataAccessMode) As ADODB.Recordset
    Dim cn As ADODB.Connection
    Dim rs As ADODB.Recordset
    
    On Error GoTo ErrorHandler

    ' 1. Connectionの確立 (実際は共通のConnection管理クラスを使うべき)
    Set cn = New ADODB.Connection
    ' ※接続文字列は環境に合わせて適切に管理すること
    cn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & CurrentProject.FullName & ";"
    cn.Open

    ' 2. カーソルロケーションの設定
    ' Client-side cursorを使用することで、Access環境下での操作性を向上させる
    cn.CursorLocation = adUseClient

    Set rs = New ADODB.Recordset

    ' 3. モードに応じたCursorTypeの切り替え
    Select Case mode
        Case Mode_ReadOnly_Fast
            ' 高速スナップショットモード
            ' 他者の変更を無視し、メモリ上のデータを高速にスキャンする
            rs.Open sql, cn, adOpenStatic, adLockReadOnly
            
        Case Mode_RealTime_Live
            ' リアルタイム・ダイナミックモード
            ' 他者の変更を追跡するが、負荷が高いことを認識して使用する
            rs.Open sql, cn, adOpenDynamic, adLockOptimistic
    End Select

    ' ConnectionオブジェクトはRecordsetが生きている間維持する必要があるため、
    ' ここではRecordsetに紐付けたまま返す(実際はConnection管理クラスで制御)
    Set GetOptimizedRecordset = rs
    Exit Function

ErrorHandler:
    ' エラー発生時はリソースを確実に解放
    If Not rs Is Nothing Then
        If rs.State = adStateOpen Then rs.Close
        Set rs = Nothing
    End If
    If Not cn Is Nothing Then
        If cn.State = adStateOpen Then cn.Close
        Set cn = Nothing
    End If
    
    ' エラー情報を呼び出し元へ伝達
    Err.Raise Err.Number, "modDataAccess.GetOptimizedRecordset", _
              "SQL実行中にエラーが発生しました: " & Err.Description
End Function

''' <summary>
''' 実務的な利用例
''' </summary>
Public Sub Sample_Usage()
    Dim rs As ADODB.Recordset
    Dim sql As String
    
    ' パターンA: 高速な集計処理(ReadOnly)
    sql = "SELECT Category, SUM(Price) FROM Sales GROUP BY Category"
    Set rs = GetOptimizedRecordset(sql, Mode_ReadOnly_Fast)
    
    Do Until rs.EOF
        Debug.Print rs!Category & ": " & rs!SumOfPrice
        rs.MoveNext
    Loop
    
    ' 後始末(重要:ライフサイクルを意識せよ)
    rs.Close
    Set rs = Nothing
    
    ' パターンB: リアルタイム性が求められる在庫確認(Live)
    sql = "SELECT ItemName, StockQty FROM Inventory WHERE StockQty < 10"
    Set rs = GetOptimizedRecordset(sql, Mode_RealTime_Live)
    
    ' ... 業務ロジック ...
    
    rs.Close
    Set rs = Nothing
End Sub

4. 最後に:アーキテクトからの警告

`CursorType` を選定する際、もう一つ忘れてはならない真理がある。それは `CursorLocation` だ。

コード内で `cn.CursorLocation = adUseClient` を指定している。これは、カーソルの制御をサーバーではなく「クライアント(Access側)」で行うことを意味する。Access VBAにおいて、大規模なデータを扱う場合はこの設定がパフォーマンスの鍵を握る。しかし、`adUseClient` を使用する場合、`adOpenDynamic` の挙動はサーバー側の挙動とは異なる(メモリ上に全データを展開しようとするため)という特性がある。

「動的カーソルを使えば、すべてが最新に見える」という幻想を捨てろ。

設計の基本は、「可能な限り、最も静的な(Static/ForwardOnly)カーソルで済ませる」ことだ。どうしても必要な時だけ、コストの高いダイナミックな手法を選択する。この「引き算の設計」こそが、堅牢で高速な業務アプリケーションを生むのだ。

君のコードが、単なる命令の羅列ではなく、洗練されたアーキテクチャであることを願う。

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