「断絶されたレコードセット」による極限のオフライン編集アーキテクチャ:ネットワーク負荷をゼロに近づける設計思想
データベースエンジニアリングの現場において、Access VBAを利用する者が直面する最大の障壁は、コードの書き方ではない。「ネットワーク・レイテンシ(遅延)」と「接続の維持コスト」である。
多くの開発者は、DAO(Data Access Objects)や、接続維持型のADO(ActiveX Data Objects)を用いて、サーバー上のテーブルを直接、あるいはリンクテーブル越しに操作する。しかし、VPN越しや不安定なLAN環境において、この「常に接続を維持する(Connection-Oriented)」アプローチは、致命的なパフォーマンス低下と、不必要な行ロックによるデッドロックを引き起こす。
本稿では、私が長年、大規模な基幹システムを設計する中で磨き上げてきた、ADOのDisconnected Recordset(非接続レコードセット)を活用した、メモリ内完結型アーキテクチャについて詳説する。これは、単なるテクニックではない。データのライフサイクルを完全に制御するための、エンジニアリングの真髄である。
1. 接続型(Connection-Oriented)の限界と、アーキテクチャの転換
従来のDAOや、ADOのデフォルト設定(`adUseServer`)では、レコードセットのカーソルはサーバー側に存在する。ユーザーが1レコードを編集し、値を書き換えるたびに、ネットワークを介して「サーバーへの通信(チャッティな通信)」が発生する。
- 通信のオーバーヘッド: 1レコードの更新ごとに、プロトコルのハンドシェイク、SQLのパース、ログの書き込みが発生する。
- リソースの占有: ユーザーが編集画面を開いている間、その接続は「開いたまま」となり、サーバー側のセッションやメモリを消費し続ける。
- ネットワーク断絶への脆弱性: 一瞬の瞬断が、未コミットのトランザクション破棄や、アプリケーションのクラッシュに直結する。
これに対し、Disconnected Recordsetは、データをクライアント側のメモリ空間に「物理的にコピー」し、通信路を即座に閉鎖する。編集はすべてローカルのRAM上で行われ、最後に「差分」のみをサーバーへ叩き込む。このパラダイムシフトが、システムの堅牢性を劇的に向上させる。
2. Disconnected Recordset の核心:`adUseClient` の支配
Disconnected Recordsetを実現するための絶対条件は、`ADODB.Recordset` オブジェクトの `CursorLocation` プロパティを `adUseClient` に設定することである。
これにより、カーソルはサーバーではなく、クライアント(VBAを実行しているマシン)のメモリ上に展開される。このとき、Recordsetは「サーバーとの紐付け」を失った、独立したメモリ上のデータ構造体へと変貌する。
アーキテクチャのライフサイクル
1. Fetch Phase: 接続を開き、必要なデータを一括取得。取得直後に接続を `Close` する。
2. Edit Phase: 接続が切断された状態で、メモリ上のRecordsetを自由自在に操作。
3. Sync Phase: 編集完了後、再度接続を開き、メモリ上の変更内容を一括でサーバーへ同期(Update)する。
3. 実装:極限まで最適化された同期エンジン
以下に、プロフェッショナルな現場で実装すべき、堅牢な同期プロセスのコード例を示す。ここでは、メモリリークを防ぐための明示的なオブジェクト解放と、エラーハンドリングの徹底を前提としている。
' --- Module: modDataEngine ---
Option Explicit
' 定数定義(参照設定が困難な環境を想定し、定数を定義)
Private Const adUseClient As Long = 3
Private Const adOpenStatic As Long = 0
Private Const adLockOptimistic As Long = 4
Private Const adBlackBox As Long = 0 ' 実際には適切なEnumを使用
''' <summary>
''' サーバーからデータをメモリへロードし、接続を即座に切断する
''' </summary>
Public Function LoadDisconnectedRecordset(ByVal strConn As String, ByVal strSQL As String) As ADODB.Recordset
Dim rs As ADODB.Recordset
Dim cn As ADODB.Connection
On Error GoTo Err_Handler
Set rs = New ADODB.Recordset
Set cn = New ADODB.Connection
' 1. カーソルロケーションをクライアント側に設定(最重要)
rs.CursorLocation = adUseClient
' 2. 接続の確立
cn.Open strConn
' 3. データの取得。Staticカーソルを使用し、メモリへのコピーを確実にする
rs.Open strSQL, cn, adOpenStatic, adLockOptimistic
' 4. 接続の即時切断。これによりネットワーク負荷を最小化する
cn.Close
Set cn = Nothing
' 5. メモリ上のレコードセットを返す
Set LoadDisconnectedRecordset = rs
Exit Function
Err_Handler:
If Not cn Is Nothing Then
If cn.State = adStateOpen Then cn.Close
Set cn = Nothing
End If
Set rs = Nothing
Err.Raise Err.Number, "modDataEngine.LoadDisconnectedRecordset", Err.Description
End Function
''' <summary>
''' メモリ上の変更をサーバーへ一括同期する
''' </summary>
Public Sub SyncDisconnectedRecordset(ByRef rs As ADODB.Recordset, ByVal strConn As String)
Dim cn As ADODB.Connection
On Error GoTo Err_Handler
' 1. 接続の再確立
Set cn = New ADODB.Connection
cn.Open strConn
' 2. トランザクションの開始(原子性の確保)
cn.BeginTrans
' 3. Recordsetに接続を再バインド
' ※Disconnected状態のRecordsetは、ActiveConnectionを再設定することで同期が可能
Set rs.ActiveConnection = cn
' 4. 変更の書き込み
' 楽観的ロック(Optimistic Concurrency)に基づき、変更分のみが送信される
rs.UpdateBatch ' 大量更新の場合はUpdateBatchが効率的
' 5. コミット
cn.CommitTrans
Debug.Print "Sync Successful."
CleanUp:
' 6. オブジェクトの明示的解放(COMオブジェクトのライフサイクル管理)
' VBAのガベージコレクションに頼らず、メモリリークを物理的に防ぐ
Set rs.ActiveConnection = Nothing
If Not cn Is Nothing Then
If cn.State = adStateOpen Then cn.Close
Set cn = Nothing
End If
Exit Sub
Err_Handler:
If Not cn Is Nothing Then
cn.RollbackTrans
Debug.Print "Sync Failed. Transaction Rolled Back. Error: " & Err.Description
End If
Resume CleanUp
End Sub
4. メモリ管理とCOMオブジェクトの「死」に関する考察
シニアエンジニアが最も警戒すべきは、VBAにおける「見えないメモリリーク」である。
ADOのオブジェクトはCOM(Component Object Model)に基づいている。VBAの変数スコープが外れたとしても、内部的な参照カウントがゼロにならない限り、メモリ上の実体は解放されない。特に `Recordset` が `Connection` を保持している状態(`ActiveConnection` がセットされている状態)で、`Connection` だけを `Nothing` にしようとしても、参照が残っているためメモリ解放は行われない。
私の設計思想では、以下の原則を徹底する。
1. 明示的な `Set = Nothing`: スコープの終了時ではなく、その役割が終わった瞬間に解放する。
2. `ActiveConnection = Nothing` の儀式: 接続を切断する前に、Recordset側の参照を先に切り離す。これは、循環参照(Circular Reference)を防ぐための鉄則である。
5. 楽観的ロックとコンフリクトへの備え
Disconnected Recordsetを採用するということは、「サーバーの状態が、クライアントがデータを読み取った後に変化している可能性」を認めることと同義である。
`adLockOptimistic` を使用する場合、ADOは内部的に、更新時に「読み取り時の値」と「現在のサーバー上の値」を比較する。もし、他のユーザーによって値が書き換えられていた場合、`rs.Update` はエラーをスローする。
このとき、システム管理者は以下の戦略を選択しなければならない。
- Overwrite(上書き): 変更を強制する(整合性リスクあり)。
- Cancel(破棄): ユーザーに再読み込みを促す(最も安全)。
- Merge(マージ): 変更されたカラムのみを特定して結合する(高度なロジックが必要)。
結言:アーキテクトの視点
Access VBAを単なる「簡易的なツール」と侮る者は、大規模運用の苦しみを理解していない。ネットワークの揺らぎ、メモリの断片化、トランザクションの衝突。これら全ての物理的な制約を計算に入れ、ソフトウェアの構造を設計すること。それこそが、真のアーキテクチャである。
Disconnected Recordsetは、その制約を克服するための強力な武器となる。接続を「持たない」ことで、接続を「支配する」。この逆説的なアプローチこそが、レガシー環境におけるモダンなデータ管理を実現する鍵となるのだ。
