【実務・中級編】【上級】Application.CurrentDbをキャッシュして、DAO接続のメモリ効率を最大化する設計パターン – Access VBA解析バイブル

スポンサーリンク

【上級】Application.CurrentDbをキャッシュして、DAO接続のメモリ効率を最大化する設計パターン

開発現場でよく見かける光景がある。
プロシージャのあちこちで `CurrentDb.OpenRecordset` が呼び出され、ループの中ですら `CurrentDb` が平然と使われているコード。

「動いているからいいだろう」?
いや、それはAccessのオブジェクトモデルとDAO(Data Access Objects)の裏側を知らなすぎる。そのコードは、目に見えないところでAccessのエンジンに過大な負荷をかけ、メモリリークの爆弾を抱えている。

今回は、プロフェッショナルなAccess開発者であれば常識として持っているべき、`Application.CurrentDb` のキャッシュ化によるメモリ効率の最大化と、堅牢なデータベース接続管理の設計パターンを伝授する。

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

多くの初中級プログラマは、`CurrentDb` をグローバルなショートカット程度に考えている。しかし、公式ドキュメントを正しく読み解けば、その認識の危うさがわかる。

`Application.CurrentDb` メソッドが実行されるたび、内部で何が起きているか?
それは、「現在のデータベースへの新しい `Database` オブジェクトのインスタンスを生成し、メモリ上にロードする」という重い処理である。

1. パフォーマンスの劣化

ループ内で `CurrentDb` を呼べば、ループの回数分だけオブジェクトの生成と破棄が繰り返される。これはDBアクセスのたびに無駄なオーバーヘッドを生む。

2. オブジェクトの参照切れとメモリの圧迫

生成された `Database` オブジェクトや、そこから派生した `Recordset` オブジェクトが適切に解放されないまま放置されると、Accessの内部メモリが圧迫され、や処置なしの「リソース不足」エラーを引き起こす。

3. トランザクションやスキーマ変更の整合性

同じプロシージャ内で複数の異なる `CurrentDb` インスタンスを参照している場合、それぞれが微妙に異なるスナップショットやロック状態を持つことになり、予期せぬ競合やロック競合の温床となる。

解決策:モジュールレベルでの「CurrentDbキャッシュ設計」

この問題を解決する最もエレガントかつ堅牢なアプローチは、「一連の処理(またはフォーム/クラスのライフサイクル)の中で、`CurrentDb` のインスタンスを一度だけ取得し、モジュールレベル変数にキャッシュして使い回す」ことだ。

さらに一歩進め、単なる変数保持にとどまらず、「必要な時に遅延初期化(Lazy Initialization)され、確実にクリーンアップされるプロパティ」としてカプセル化するのが、プロダクションコードにおけるベストプラクティスとなる。

【実装例】プロダクション品質のDAOキャッシュ・モジュール

以下のコードは、エラーハンドリング、オブジェクトの確実な解放(破棄)、そしてキャッシュ機構を網羅した標準モジュール(例: `basDatabase`)の実装である。

Option Compare Database
Option Explicit

‘ =================================================================
‘ モジュール名: basDatabase
‘ 概要: DAO.Databaseのインスタンスをキャッシュし、メモリ効率を最大化する設計
‘ =================================================================

‘ モジュールレベルのプライベート変数(キャッシュ保持用)
Private m_CachedDB As DAO.Database

ええっと、プロパティを公開しつつ、実体のライフサイクルを完全に制御する。

”’

”’ キャッシュされたDAO.Databaseオブジェクトを取得する。
”’ まだ生成されていない、または閉じられている場合は新しく生成する。
”’

Public Property Get CurrentDatabase() As DAO.Database
On Error GoTo ErrorHandler

‘ キャッシュが空、もしくはすでに解放されている場合は再取得
If m_CachedDB Is Nothing Then
Set m_CachedDB = Application.CurrentDb
Else
‘ 念のため、オブジェクトが有効かどうかの疎通確認(必要に応じて)
‘ ※Accessの仕様上、外部要因で無効化されるケースへの保険
If m_CachedDB.Name = “” Then
Set m_CachedDB = Application.CurrentDb
End If
End If

Set CurrentDatabase = m_CachedDB
Exit Property

ErrorHandler:
‘ 予期せぬエラー時は強制的に再取得を試みる
Set m_CachedDB = Application.CurrentDb
Set CurrentDatabase = m_CachedDB
End Property

”’

”’ キャッシュされたデータベース接続を強制的に解放する。
”’ アプリケーションの終了時や、長時間のバッチ処理の区切りで明示的に呼び出すこと。
”’

Public Sub ReleaseCachedDatabase()
On Error Resume Next
If Not m_CachedDB Is Nothing Then
m_CachedDB.Close
Set m_CachedDB = Nothing
End If
On Error GoTo 0
End Sub

”’

”’ 【実用サンプル】キャッシュを活用した高速データ処理プロシージャ
”’

Public Sub ExecuteBulkProcessSample()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim startTime As Double

startTime = Timer
On Error GoTo ErrorHandler

‘ ① キャッシュされたプロパティ経由でDatabaseを取得(無駄なインスタンス化を回避)
Set db = CurrentDatabase

‘ トランザクションの開始(キャッシュされた同一インスタンスだからこそ安全)
db.BeginTrans

Set rs = db.OpenRecordset(“T_SampleMaster”, dbOpenDynaset)

Do While Not rs.EOF
‘ — 実際の業務処理(例:ステータスの更新) —
rs.Edit
rs!ProcessedFlag = True
rs!LastUpdated = Now
rs.Update

rs.MoveNext
Loop

db.CommitTrans
MsgBox “処理が完了しました。実行時間: ” & Format(Timer – startTime, “0.00秒”), vbInformation

CleanUp:
‘ Recordsetのみを確実に閉じる(dbはキャッシュなのでここでは絶対にCloseしない!)
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
‘ db変数はモジュール側で管理するため、ここでは Set db = Nothing のみに留める
Set db = Nothing
Exit Sub

ErrorHandler:
‘ エラー時はロールバック
On Error Resume Next
If Not db Is Nothing Then db.Rollback
On Error GoTo 0

MsgBox “エラーが発生しました: ” & Err.Description, vbCritical
Resume CleanUp
End Sub

架构の急所:開発者が絶対に守るべき3つの鉄則

上記のコードから読み取れる、シニアエンジニアとしての重要な設計思想を解説する。

1. キャッシュした `Database` オブジェクトを勝手に `Close` しない

ここが最大の罠だ。通常のコードでは `Set db = CurrentDb` の後に `db.Close` を書く教えが多いが、モジュールレベルでキャッシュしたインスタンスを途中で `Close` してしまうと、次回以降のアクセスで致命的な実行時エラー(エラー 3014: などのリソース切れや無効な参照)を引き起こす。
キャッシュした `Database` のクローズは、アプリケーションの終了時、あるいは一連のバッチ処理の完全に終わるタイミング(`ReleaseCachedDatabase`)で一括して行うこと。個別のプロシージャ内では `Set db = Nothing`(参照の解放のみ)に留めるのが鉄則だ。

2. レコードセット(`Recordset`)とデータベース(`Database`)の寿命を分離する

データベース接続はキャッシュして使い回すが、そこから派生する `Recordset` は処理ごとに必ず生成し、使い終わったら即座に `.Close` し `Set rs = Nothing` で破棄する。
この「DB接続は永続的、Recordsetは使い捨て」というライフサイクルの分離こそが、メモリリークを防ぐ唯一の解である。

3. トランザクションの一元管理

複数の処理がバラバラの `CurrentDb` を使ってトランザクションを張ると、デッドロックや整合性の崩壊を招く。キャッシュされた単一の `Database` インスタンスをすべてのレイヤーで共有することで、トランザクションのスコープが明確になり、堅牢なACID特性を維持できる。

現場で即効性を発揮する運用シーン

この設計パターンは、特に以下のようなシチュエーションで圧倒的なパフォーマンスの差を生む。

  • 数万件規模のレコードをループ処理で一括更新・登録するバッチ処理
  • 複雑なリレーションを持つ複数のフォームやレポートが同時に開くダッシュボード画面
  • 外部連携のため、短時間に膨大なクエリを発行するデータ同期モジュール

「なんとなく `CurrentDb` を書く」という習慣を捨て、オブジェクトのライフサイクルをコードで完全に支配すること。それこそが、Accessの限界を引き出し、実務で絶対に壊れないシステムを構築するための王道である。

明日からのコードには、ぜひこの「CurrentDbキャッシュ設計」を導入してほしい。パフォーマンスモニターの数値、そして動作の軽快さに驚くはずだ。

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