Access VBAを掌握する極限の知見:`Application.CurrentDb`をキャッシュすべき理由とDAOの裏側
レガシーシステムの最前線、あるいはミッションクリティカルな現場において、Access VBAは今なおその機動力を発揮している。しかし、多くの開発者は「動けば正義」というアマチュアリズムの呪縛から逃れられず、オブジェクトモデルのライフサイクルを無視したコードを量産している。
その代表例が、ループ内での無秩序な `CurrentDb` の呼び出しだ。
本稿では、`Application.CurrentDb` が内部で何を行っているのかというオブジェクトモデルの真髄に迫り、DAO.Database変数をキャッシュするデザインパターンがなぜ圧倒的なパフォーマンス差を生むのか、そのメカニズムをアーキテクトの視点から解き明かす。
—
1. `CurrentDb` の正体:なぜ毎回呼ぶと遅いのか?
多くのVBAプログラマは、`CurrentDb` を単なる「現在のデータベースへのショートカット」程度に考えている。しかし、Accessのオブジェクトモデルにおいて、`CurrentDb` メソッドは決して軽量ではない。
内部プロセスの真実
`Application.CurrentDb` を呼び出すたびに、Accessエンジン(ACE/Jet)の内部では以下の重厚な処理が走っている。
1. COMインターフェースの新規インスタンス化: 呼び出し元に対し、常に新しい `DAO.Database` オブジェクトのラッパー(プロキシ)が生成され、返される。
2. 内部キャッシュの無効化と再構築: システムは現在のデータベースへの接続状態を検証し、内部的なロックやスキーマ情報の整合性を確認する。
3. ガベージコレクションの負荷: ループ内でこれが何万回も行われると、VBAのランタイムとCOMコンポーネントの間で膨大なオブジェクトの生成と破棄が繰り返され、メモリリークに近いヒープ断片化とCOMの参照カウンタの暴走を引き起こす。
対して、`DBEngine.Workspaces(0).Databases(0)` を指す `CurrentDb` と異なり、一度変数に参照を保持(キャッシュ)した `DAO.Database` は、メモリ上で直接COMポインタを指し続けられるため、こうした毎回のオーバーヘッドを完全にバイパスできる。
—[AD: 現場で使えるAccess高速化の極意]#—
2. 実装パターン:モジュールレベル・キャッシュの設計
では、実務でどのようにこれを実装すべきか。
「毎回 `CurrentDb` を書かない」というレベルの話ではない。アプリケーションのライフサイクル、あるいはトランザクションのスコープに合わせて、`DAO.Database` 変数を適切に管理する「データベース・コンテキスト・マネージャ」の概念を導入する。
以下のコードは、数万件のレコードを処理するバッチ処理において、`CurrentDb` をキャッシュした場合とそうでない場合のパフォーマンス差を意識した、プロダクション品質のモジュールである。
Option Compare Database
Option Explicit
‘ =========================================================================
‘ クラス名 / モジュール名: MdlDatabaseManager
‘ 概要: DAO.Databaseのライフサイクルを管理し、オーバーヘッドを極限まで排除する
‘ =========================================================================
‘ モジュールレベルのプライベート変数(キャッシュ保持用)
Private m_CachedDB As DAO.Database
”
‘ キャッシュされたDAO.Databaseインスタンスを取得する(遅延初期化パターン)
‘ @return DAO.Database
‘ =========================================================================
Public Function GetWorkingDB() As DAO.Database
‘ 参照が解放されている、あるいは未初期化の場合のみCurrentDbを叩く
If m_CachedDB Is Nothing Then
Set m_CachedDB = Application.CurrentDb
Else
‘ エラーハンドリングや接続切断検知を入れる場合はここに記述
‘ 例: 意図せず閉じられた場合のフォールバック等
End If
Set GetWorkingDB = m_CachedDB
End Function
”
‘ キャッシュを明示的に解放する(終了時やトランザクション確定時)
‘ =========================================================================
Public Sub ReleaseWorkingDB()
On Error Resume Next
If Not m_CachedDB Is Nothing Then
‘ 明示的な解放(COMオブジェクトの参照カウンタをデクリメント)
Set m_CachedDB = Nothing
End If
On Error GoTo 0
End Sub
”
‘ 【実践】数万件のループ処理における極限最適化サンプル
‘ =========================================================================
Public Sub ExecuteHighSpeedBatch()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim i As Long
Dim startTime As Double
startTime = Timer
‘ 1. キャッシュからインスタンスを取得(ループ外で1回のみ)
Set db = GetWorkingDB()
‘ トランザクションの開始(ディスクI/Oを極限まで削減)
db.BeginTrans
On Error GoTo ErrorHandler
Set rs = db.OpenRecordset(“T_PerformanceTest”, dbOpenDynaset)
For i = 1 to 50000
rs.AddNew
rs!LogMessage = “Process ID: ” & i & ” – ” & Format(Now, “hh:mm:ss”)
rs!ProcessedDate = Now
rs.Update
Next i
db.CommitTrans
Debug.Print “処理完了時間: ” & (Timer – startTime) & ” 秒”
‘ クリーンアップ
rs.Close
Set rs = Nothing
‘ ※db(m_CachedDB)はここでは閉じない。アプリケーション終了時やセッション終了時に解放する。
Exit Sub
ErrorHandler:
db.Rollback
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
End Sub
—
3. アーキテクチャの深化:メモリ最適化とCOMの解放戦略
VBAはガベージコレクタ(GC)を持たない言語である。.NETやJavaの感覚でコードを書くと、背後でCOMオブジェクトがメモリ上に居座り続け、Accessのプロセスサイズが肥大化していく。
オブジェクトの明示的解放の鉄則
1. ローカル変数の即時破棄: ループ内で開いた `Recordset` や `QueryDef` は、スコープを抜ける前、あるいは不要になった時点で必ず `rs.Close` し、`Set rs = Nothing` を行うこと。
2. `CurrentDb` の戻り値を直接ループ条件やメソッド引数に使わない:
‘ 【悪魔のようなアンチパターン】
‘ ループの評価ごとにCurrentDbがコールされ、COMラッパーがゴミ山の如く生成される
For i = 1 To CurrentDb.TableDefs.Count
正しくは、一度変数に受けてからプロパティを叩くべきだ。
Dim db As DAO.Database
Set db = GetWorkingDB()
For i = 1 To db.TableDefs.Count
—
4. レガシー環境とシステム間連携における実践的知見
もしこのAccessが、外部のSQL ServerやOracleといったRDBMSとODBCリンクテーブルで接続されている場合、`CurrentDb` の乱用はデータベースサーバ側のコネクションプールの枯渇を招く。
リンクテーブルに対して何度も `CurrentDb` 経由でクエリを実行すると、ACEエンジンは内部的に接続セッションの確立・切断を頻繁に試み、ネットワークレイヤーに甚大な負荷を与える。DAO.Database変数をキャッシュし、トランザクション(`BeginTrans` / `CommitTrans`)でラップすることは、単なるVBAの高速化に留まらず、RDBMS側の負荷軽減およびデッドロック回避の決定打となる。
—
5. 結び:シニアエンジニアに求められるコードの美学
「動けばいい」の時代は終わった。
数万件のレコードを扱う現場において、0.1秒の積み重ねは、システムの寿命を延ばすか、あるいは「使えないレガシーゴミ」として早々にリプレイスされるかの分かれ道である。
`Application.CurrentDb` の背後にあるコストを理解し、オブジェクトのライフサイクルを完全に支配すること。それこそが、Access VBAという一見古びた環境から、極限のパフォーマンスを引き出すプロフェッショナルの仕事である。
明日から君のコードベースにある `CurrentDb` の乱用箇所をすべて洗い出し、キャッシュ機構へとリファクタリングせよ。システムは必ず、圧倒的な速度をもって応えてくれるはずだ。
