【上級】Application.CurrentDbをキャッシュして、DAO接続のメモリ効率を最大化する設計パターン
レガシーシステムの最前線に立ち続けるエンジニアであれば、一度は目にしたことがあるだろう。
プロシージャのあちこちに散らばる `CurrentDb` の呼び出し、そしてその背後でひそやかに繰り返されるCOMオブジェクトの生成と破棄のダンスを。
「動いているから触るな」の精神で放置されたコードベースは、数万件のレコードを処理するループの中で、自らメモリリークの爆弾を育てている。
今回は、Access VBAにおけるDAO(Data Access Objects)の挙動の深層に踏り込み、`Application.CurrentDb` をモジュールレベルでキャッシュすることで、メモリ効率と実行パフォーマンスを極限まで引き上げるアーキテクチャを解説する。
—
1. なぜ `CurrentDb` の多用は「悪」なのか?
多くのVBAプログラマは、データベースへアクセスする際、深く考えずに次のようなコードを書く。
‘ 悪臭を放つアンチパターンの例
Dim i As Long
For i = 1 to 10000
Dim rs As DAO.Recordset
‘ ループのたびにCurrentDbを呼び出す
Set rs = CurrentDb.OpenRecordset(“SELECT FROM T_Master WHERE ID = ” & i)
‘ … 処理 …
rs.Close
Set rs = Nothing
Next i
このコードの何が問題か。
`Application.CurrentDb` は、単なるプロパティの参照ではない。これを呼び出すたびに、Accessの内部エンジン(ACE / JET)に対して新しい DAO.Database オブジェクトのインスタンス要求が行われ、メモリ上に新たなCOMラッパーが生成される。
ループのたびにインスタンスが生成され、スコープを抜けるときに破棄される(ガベージコレクションのタイミングやVBAの参照カウンタの挙動に依存する)ため、メモリ断片化(Fragmentation)を引き起こし、最悪の場合はメモリリークや「リソース不足」エラーの原因となる。
これに対し、`CurrentDb()` メソッドと `DBEngine(0)(0)` の挙動の違いを理解している者は少ない。
- `DBEngine(0)(0)`: デフォルトのワークスペースにおける現在開いているデータベースのキャッシュされた参照を返す。高速だが、マルチユーザ環境やトランザクション処理、スキーマ変更の検出において罠がある。
- `CurrentDb`: 常に最新のシステム状態を反映した「新しい」DAO.Databaseオブジェクトを返す。安全だが、呼び出すたびにコストがかかる。
安全性を担保しつつ、コストを排除する唯一の解が「明示的なキャッシュとライフサイクル管理」である。
—
2. シニアが実装する「DAO接続キャッシュ」設計パターン
実務で耐えうる堅牢なシステムを構築するためには、データベース接続をシングルトン(Singleton)に近いスコープで管理し、適切なタイミングで解放するクラスまたは標準モジュール設計が必要となる。
ここでは、プロパティプロシージャ(Property Get)をラップした「スマートキャッシュパターン」を提示する。
実装コード:`modDatabaseManager`(標準モジュール)
Option Explicit
‘ モジュールレベルのプライベート変数(これがキャッシュの本体)
Private m_CachedDB As DAO.Database
‘ ==============================================================================
‘ 概要: キャッシュされたDAO.Databaseオブジェクトを取得する。
‘ 未初期化または無効化されている場合のみ、新規インスタンスを生成する。
‘ ==============================================================================
Public Property Get CurrentDB_Cached() As DAO.Database
On Error GoTo ErrorHandler
‘ キャッシュ変数がNothing、またはすでに閉じられている(Invalid)か判定
If m_CachedDB Is Nothing Then
Set m_CachedDB = Application.CurrentDb
Else
‘ 接続が生きているかどうかの健全性チェック(簡易的にプロパティにアクセス)
Dim testName As String
testName = m_CachedDB.Name
End If
Set CurrentDB_Cached = m_CachedDB
Exit Property
ErrorHandler:
‘ エラーが発生した場合(DBが予期せぬ理由で閉じられた等)、再取得を試みる
Set m_CachedDB = Application.CurrentDb
Set CurrentDB_Cached = m_CachedDB
End Property
‘ ==============================================================================
‘ 概要: アプリケーション終了時やトランザクションの区切りで必ず呼び出す解放メソッド
‘ ==============================================================================
Public Sub ReleaseCachedDB()
On Error Resume Next
If Not m_CachedDB Is Nothing Then
‘ DAO.Databaseの明示的クローズは不要だが、参照の解放を確実に行う
Set m_CachedDB = Nothing
End If
On Error GoTo 0
End Sub
—
3. オブジェクトのライフサイクルとメモリ最適化の極意
VBAのメモリ管理は、一見すると自動化されているように見えて、その実、COMの参照カウント(Reference Counting)の厳格なルールに支配されている。
特にDAOオブジェクトは、以下の鉄則を破ると容赦なくメモリリークを起こす。
1. レコードセットやクエリデフィニションの親関係の意識
`CurrentDb.OpenRecordset` で作成したレコードセットは、親である `Database` オブジェクトへの参照を保持し続ける。キャッシュした `Database` を使う場合、そこから派生する `Recordset` や `QueryDef` は、使い終わったら即座に `.Close` し、変数に `Nothing` を代入して参照カウントをゼロにしなければならない。
2. 終了処理(Terminate)のフック
アプリケーションの終了時(あるいはメインフォームの閉じるイベント)において、必ず `ReleaseCachedDB` を呼び出し、モジュールレベル変数の参照を断ち切る必要がある。これを怠ると、Accessのプロセス(MSACCESS.EXE)がタスクマネージャー上にゾンビとして残る原因となる。
—
4. 実戦投入:バッチ処理におけるパフォーマンス比較
このキャッシュパターンを適用したバッチ処理の模範例を示す。数千件、数万件のレコードを操作するバルク処理において、その真価が発揮される。
Public Sub ExecuteBulkProcess()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim startTime As Double
startTime = Timer
‘ キャッシュ経由でデータベース参照を取得(高速かつ安全)
Set db = CurrentDB_Cached
‘ トランザクションの開始(パフォーマンス劇的向上のお供)
db.BeginTrans
On Error GoTo RollbackAndExit
Set rs = db.OpenRecordset(“T_TargetTable”, dbOpenDynaset)
Do Until rs.EOF
rs.Edit
rs!ProcessedFlag = True
rs!ProcessedDate = Now()
rs.Update
rs.MoveNext
Loop
db.CommitTrans
Debug.Print “処理完了: ” & Format(Timer – startTime, “0.00秒”)
CleanUp:
‘ 派生オブジェクトの解放(親DBはキャッシュしているので閉じない!)
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
‘ 注意: ここで db = Nothing はしてはならない(モジュール変数を破壊するため)
Exit Sub
RollbackAndExit:
db.Rollback
MsgBox “エラー発生のためロールバックしました: ” & Err.Description, vbCritical
Resume CleanUp
End Sub
—
5. チーフアーキテクトからの提言
レガシーシステムにおけるパフォーマンスチューニングの本質は、新しい技術を導入することではなく、既存のプラットフォーム(Access/JET/ACE)の挙動の無駄を極限まで削ぎ落とすことにある。
`Application.CurrentDb` のキャッシュパターンは、コードの行数をわずかに増やすだけで、データベースエンジンへの無駄な負荷を消し去り、メモリ使用量を安定させ、長期間稼働するAccessシステムの信頼性を劇的に向上させる。
職人としての誇りを持つならば、動くだけのコードに満足せず、メモリの呼吸音まで聞こえるような洗練されたアーキテクチャを実装し続けてほしい。
