Access VBAを掌握する極限の知見:ADO切断型レコードセット(Disconnected Recordset)によるオフライン超高速演算の極意
シニアエンジニア、そして数々のレガシーシステムを死守してきたアーキテクト諸君。
日々の業務で「Accessのパフォーマンス限界」に頭を悩ませていないか?
ネットワーク越しのSQL ServerやOracle、あるいは肥大化したJet/ACEデータベースに対し、細切れのクエリを乱発し、ネットワークラウンドトリップの嵐によってシステムを徐々に窒息させてはいないか。
DAO(Data Access Objects)がAccessのネイティブエンジン(Jet)の血肉であるならば、ADO(ActiveX Data Objects)は異世界への接続の窓である。そして、そのADOが持つ真のポテンシャル――いや、禁断の奥義と言っても過言ではないのが、「切断型レコードセット(Disconnected Recordset)」である。
今回は、サーバー接続を断ち切った状態でメモリ上にデータを展開し、極限のスピードでインメモリ処理を行い、最後に一括してデータベースへ同期(バッチ更新)させるアーキテクチャの真髄を解説する。
—
1. なぜ「切断(Disconnect)」が必要なのか?
通常、ADOのRecordsetオブジェクトは、Connectionオブジェクトが生きた状態(ステートフル)で保持される。この状態では、以下のような致命的なコストが発生する。
- コネクションリソースの枯渇: 同時接続数に制限のあるRDBにおいて、VBAクライアントが接続を占有し続ける。
- ロック競合とトランザクションの肥大化: ユーザーが画面で悩んでいる間、サーバー側でロックが保持され、他のトランザクションをブロックする。
- ネットワークI/Oのボトルネック: 1レコード更新するたびにネットワークを往復する無駄。
これを解決するのが切断型レコードセットだ。
データの取得(`Open`)が終わった瞬間、あえて接続(`ActiveConnection`)を `Nothing` に切り離す。これにより、レコードセットは完全に独立したインメモリのデータグリッド(一種のインメモリデータベース)へと変貌を遂げる。
サーバーの呪縛から解放されたデータは、VBAのメモリ空間上で爆速で検索・編集・演算が可能となる。
—
2. アーキテクチャの全体像:接続・切断・再接続のライフサイクル
切断型レコードセットを操るには、以下の厳密なライフサイクル管理が求められる。オブジェクトの破棄を怠れば、Accessは確実にメモリリークを起こし、やがてヒープ領域を食いつぶしてクラッシュする。
[RDB / SQL Server]
│
▼ (1. 接続 & データ取得)
[ADO Connection]
│
▼ (2. ActiveConnection = Nothing で切断)
[Disconnected Recordset (メモリ上に常駐)]
│
├─► 高速なインメモリ検索・編集・追加・削除
│
▼ (3. 再接続 & UpdateBatch)
[RDB / SQL Server (一括同期)]
—
3. 実装コード:極限のメモリ最適化とバッチ更新の全貌
以下に、実業務でそのまま使える堅牢なクラスモジュールレベルのコードを示す。
エラーハンドリング、トランザクション、そして何よりも「オブジェクトの明示的かつ確実な解放(Destruction)」を徹底した実装だ。
Option Explicit
‘ ==============================================================================
‘ 担当: チーフアーキテクト
‘ 概要: ADO切断型レコードセットを用いたオフライン一括更新処理
‘ 特徴: ネットワークコストの最小化と、厳格なリソース解放ライフサイクル管理
‘ ==============================================================================
Public Sub ExecuteDisconnectedBatchProcess()
Dim cn As Object _ ‘ ADODB.Connection
Dim rs As Object _ ‘ ADODB.Recordset
Dim connString As String
Dim sql As String
‘ 接続文字列の定義(例: SQL Server用 OLEDBプロバイダ)
connString = “Provider=SQLOLEDB.1;Password=YourPassword;Persist Security Info=True;” & _
“User ID=YourUser;Initial Catalog=YourDatabase;Data Source=YourServer”
‘ 1. コネクションの生成とオープン
Set cn = CreateObject(“ADODB.Connection”)
cn.CursorLocation = 3 ‘ adUseClient (※切断型にはクライアントサイドカーソルが必須)
cn.ConnectionTimeout = 30
cn.Open connString
sql = “SELECT EmployeeID, LastName, Salary FROM Employees WHERE Department = ‘Sales'”
Set rs = CreateObject(“ADODB.Recordset”)
On Error GoTo ErrorHandler
‘ 2. データの取得 (バッチ最適化カーソルを指定)
‘ adOpenStatic = 3, adLockBatchOptimistic = 4
rs.Open sql, cn, 3, 4
‘ ==========================================================================
‘ 3. 【核心】サーバーとの接続を完全に切断する
‘ ==========================================================================
Set rs.ActiveConnection = Nothing
‘ この瞬間、cn (Connection) は不要になるため、即座に閉じて解放しても良い。
‘ これにより、RDB側の接続スレッドを即時解放できる。
cn.Close
Set cn = Nothing
‘ ==========================================================================
‘ 4. メモリ上(オフライン)でのデータ操作
‘ ==========================================================================
‘ ネットワーク負荷を一切気にせず、メモリ上で高速にループ・編集を行う
Do While Not rs.EOF
‘ 例: Sales部門の給与を10%ベースアップ
rs.Fields(“Salary”).Value = rs.Fields(“Salary”).Value 1.1
‘ 必要であれば新規追加や削除も可能
‘ rs.AddNew
‘ rs.Fields(“LastName”).Value = “Temporary”
‘ rs.Update
rs.MoveNext
Loop
‘ ==========================================================================
‘ 5. 再接続と一括同期 (Batch Update)
‘ ==========================================================================
‘ 更新をサーバーに書き戻すために、再度コネクションを確立する
Set cn = CreateObject(“ADODB.Connection”)
cn.Open connString
‘ トランザクションの開始(データ整合性の担保)
cn.BeginTrans
‘ コネクションの再割り当て
Set rs.ActiveConnection = cn
‘ 変更分をサーバーへ一括送信(Batch Update)
rs.UpdateBatch
‘ コミット
cn.CommitTrans
MsgBox “切断型レコードセットによる一括更新が正常に完了しました。”, vbInformation
GoTo Finally
ErrorHandler:
‘ エラー発生時のロールバック
If Not cn is Nothing Then
If cn.State = 1 Then cn.RollbackTrans
End If
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical
Finally:
‘ ==========================================================================
‘ 6. 厳格なオブジェクトの破棄(メモリリークの完全阻止)
‘ ==========================================================================
On Error Resume Next
If Not rs Is Nothing Then
If rs.State = 1 Then rs.Close
Set rs = Nothing
End If
If Not cn Is Nothing Then
If cn.State = 1 Then cn.Close
Set cn = Nothing
End If
On Error GoTo 0
End Sub
—
4. シニアエンジニアが押さえるべき「罠」と極限の知見
このテクニックを実戦投入するにあたり、教科書には載っていない「現場の教訓」をいくつか伝授しよう。
① `CursorLocation = adUseClient (3)` は絶対の戒律
サーバーサイドカーソル(`adUseServer`)のまま `ActiveConnection = Nothing` を実行しようものなら、エラーが発生するか、メモリ上にデータが正しくキャッシングされない。
クライアントサイドカーソルを指定することで、Jet/ACEやOLEDBプロバイダはローカルメモリ(または一時ファイル)に結果セットを丸ごとスプールする。この挙動を理解していないと、大規模データでメモリ不足(Out of Memory)を引き起こす。
② 主キー(Primary Key)の存在意義
`UpdateBatch` を実行する際、ADOは内部的に「どのレコードが変更されたのか」を特定するために、元テーブルの主キー情報を必要とする。
SELECT句に主キーが含まれていない場合、`UpdateBatch` は一意性を担保できずにエラーを吐くか、最悪の場合、意図しない行を上書きするデータベースの破壊テロを引き起こす。切断型クエリを発行する際は、必ず主キー(ID等)を含めること。
③ 競合制御(Concurreny Control)の設計
切断型レコードセットは、データを取得してから書き戻すまでの間、他のユーザーが同じデータを書き換えている可能性がある(楽観的同時実行制御:Optimistic Concurrency)。
`UpdateBatch` 実行時にコンフリクトが発生した場合のエラーハンドリング(`rs.Resync` や `rs.Status` の走査)を実装しておかないと、エンタープライズ環境では痛い目を見る。実運用では、タイムスタンプ列(RowVersion等)をテーブルに持たせておくことがアーキテクチャ上の定石である。
—
5. 総括
Access VBAは、しばしば「おもちゃの言語」と揶揄される。しかし、それは言語の限界ではなく、使う側のアーキテクチャの理解度不足に起因することがほとんどだ。
ADOの切断型レコードセットを自在に操ることで、Accessは単なる「ローカルDBのフロントエンド」から、「高速なインメモリ・データプロセッサ」へと進化する。ネットワークの枷を外し、メモリを支配せよ。
それが、レガシーの荒野を生き抜くエンジニアの唯一にして最大の武器となる。
