【実務・中級編】【上級】Application.CurrentDbをキャッシュすべき理由:DAO.Database変数の再利用によるパフォーマンス最適化 – Access VBA解析バイブル

スポンサーリンク

【上級】Application.CurrentDbをキャッシュすべき理由:DAO.Database変数の再利用によるパフォーマンス最適化

開発現場で、Access VBAのコードレビューをしていると、今でもこんなコードが平然と書かれているのを見かける。

‘ 【アンチパターン】ループやプロシージャの都度、CurrentDbを叩く愚行
For i = 1 to 10000
CurrentDb.Execute “UPDATE T_Stock SET Quantity = Quantity – 1 WHERE ID = ” & i, dbFailOnError
Next i

動く。確かに動く。しかし、プロのエンジニアであれば、このコードを見た瞬間に冷や汗をかくべきだ。

今回は、Access VBAのパフォーマンスチューニングにおいて最も基本的かつ破壊的な効果を生む「`CurrentDb`のキャッシュ(DAO.Database変数の再利用)」について、Accessの内部構造の裏側まで踏み込んで徹底解説する。

なぜ `CurrentDb` の多用は「悪」なのか?

多くの開発者は、`CurrentDb` を単なる「現在開いているデータベースを指すショートカット」程度に考えている。しかし、その認識がシステムのパフォーマンスを静かに、確実に蝕んでいく。

1. `CurrentDb` メソッドの裏で起きている重い処理

`Application.CurrentDb` は、呼び出されるたびに以下の処理を実行している。

1. 新しい `DAO.Database` オブジェクトのインスタンス生成
2. カレントデータベース(MDB/ACCDB)の内部コンテキストへの再接続・参照解決
3. オブジェクトの破棄時のガベージコレクションの発生

つまり、これをループ内や複数プロシージャで何度も呼び出すということは、「毎回わざわざデータベースへの接続ハンドルを新しく開き直し、使い終わったら即座に捨てている」のと同じことだ。数回なら人間には知覚できないが、数千件のレコード処理や、複雑な画面描画イベントが絡むシステムでは、これが致命的なボトルネックとなる。

2. 「暗黙の再接続」がもたらすメモリリークと不安定さ

さらに厄介なのが、`CurrentDb` は呼び出すたびに「別個のDAO.Databaseインスタンス」を返すという仕様だ。
これにより、以下のような深刻な問題を引き起こす。

  • メモリの無駄な消費とフラグメンテーション
  • レコードセットやクエリDefsの参照ズレによる予期せぬエラー
  • Jet/ACEエンジンの内部キャッシュの効き目低下

決定的な解決策:DAO.Database変数のキャッシュ設計

この問題を解決する設計思想は極めてシンプルだ。
「最初に一度だけ `CurrentDb` からオブジェクトを取得し、それをローカルまたはモジュールレベルの変数に保持(キャッシュ)し、スコープの間はそれを使い回す」

これだけで、接続のオーバーヘッドは劇的に消滅する。

実務で使える堅牢なプロダクションコード例

大規模な業務システムや、大量のトランザクションをさばくデータ処理モジュールでは、以下のような設計パターンを標準採用してほしい。

Option Compare Database
Option Explicit

‘ =================================================================
‘ モジュールレベルでのDAO.Databaseキャッシュ変数
‘ =================================================================
Private m_db As DAO.Database


‘ データベース接続インスタンスを取得する(遅延初期化 / Singletonパターン風)
‘ 常に有効な接続を保証し、不必要なインスタンス生成を防ぐ
‘ =================================================================
Private Function GetAppDatabase() As DAO.Database
On Error GoTo ErrorHandler

‘ すでにインスタンスが存在し、かつ有効であるか確認する
‘ (※長時間のアイドル状態などで切断されている場合のフェイルセーフ)
If m_db Is Nothing Then
Set m_db = Application.CurrentDb
Else
‘ 接続が生きているか簡単なプロパティ参照でテスト
Dim testName As String
testName = m_db.Name
End If

Set GetAppDatabase = m_db
Exit Function

ErrorHandler:
‘ エラーが発生した場合は再接続を試みる
Set m_db = Application.CurrentDb
Set GetAppDatabase = m_db
End Function


‘ キャッシュを活用した高速バルク処理のサンプル
‘ =================================================================
Public Sub ExecuteBulkUpdateSample()
Dim db As DAO.Database
Dim ws As DAO.Workspace
Dim i As Long

‘ 処理開始時間の計測(デバッグ用)
Dim startTime As Double
startTime = Timer

‘ トランザクションとキャッシュの組み合わせで爆発的な速度向上を生む
Set ws = DBEngine.Workspaces(0)
ws.BeginTrans

On Error GoTo TransactionError

‘ ★ここでキャッシュされたDatabase参照を取得(ループ外で1回のみ)
Set db = GetAppDatabase()

For i = 1 to 5000
‘ 毎回 CurrentDb を叩くのではなく、キャッシュされた db 変数を使用
db.Execute “UPDATE T_Stock SET LastCheckedDate = Date() WHERE ID = ” & i, dbFailOnError
Next i

ws.CommitTrans
MsgBox “処理完了: ” & Format(Timer – startTime, “0.00秒”), vbInformation
Exit Sub

TransactionError:
ws.Rollback
MsgBox “エラー発生のためロールバックしました: ” & Err.Description, vbCritical
End Sub


‘ クラスやモジュールの終了時に必ず参照を解放する
‘ =================================================================
Public Sub TerminateDatabase()
If Not m_db Is Nothing Then
m_db.Close
Set m_db = Nothing
End If
End Sub

設計上の重要な注意点(プロフェッショナルの知見)

このキャッシュパターンを導入するにあたり、現場のエンジニアが押さえておくべき「絶対のルール」がある。

1. `CurrentDb` と `DBEngine.Workspaces(0).Databases(0)` の違い

よく似た表現に `CurrentDb()` と `DBEngine.Workspaces(0).Databases(0)` (または `CodeDb`)がある。

  • `CurrentDb()` は、呼び出されるたびに新規のインスタンスを返す。
  • `Databases(0)` は、Access内部ですでに開かれている単一の永続インスタンスを返す。

「じゃあ `Databases(0)` を使えば最初からキャッシュされているのでは?」と思うかもしれないが、ここには大きな罠がある。`Databases(0)` は、マルチユーザー環境や複雑な非同期処理、フォームのレコードソースとの競合において、予期せぬロックや「インデックスが見つかりません」といった不可解なエラーを引き起こすリスクが高い。

そのため、「`CurrentDb` で取得したインスタンスを、安全なスコープ内で明示的に変数に保持して使い回す」というアプローチが、最も安全かつパフォーマンスが高いベストプラクティスとなる。

2. オブジェクトのライフサイクル管理を徹底する

モジュールレベルやグローバル変数に `DAO.Database` を保持する場合、アプリケーションの終了時やエラーハンドリングの出口で、必ず `Set m_db = Nothing` によってメモリを解放する習慣をつけなければならない。これを怠ると、Accessのプロセスがメモリ上に残留し、ファイルロック(.laccdbが消えない現象)の原因となる。

まとめ:ワンランク上のAccess開発へ

「動けばいい」という妥協のコードから、「限界まで最適化された堅牢なアーキテクチャ」へ。
たったこれだけの設計変更、つまり `CurrentDb` のキャッシュ化とトランザクションの適切な組み合わせにより、処理速度が数倍〜数十倍に跳ね上がるケースを私は何度も目の当たりにしてきた。

プログラミングの美しさは、こうした細部へのこだわり宿る。ぜひ明日の開発から、あなたのコードベースにこの「キャッシュ戦略」を組み込んでほしい。ユーザーが体感するストレスは消え去り、あなたの書くコードへの信頼は揺るぎないものになるはずだ。

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