Accessの死を招く「動的QueryDef」の墓場を掃除せよ
Accessが突然肥大化し、数ヶ月で数GBに膨れ上がり、ついには`.accdb`が破損する。この悲劇の元凶の多くは、開発者が安易に生成した「使い捨てのQueryDef」にある。
多くの初学者は、SQLを動的に組み立てる際に`CurrentDb.CreateQueryDef`を乱発する。だが、これはAccessのシステムテーブル(MSysObjects)にゴミを蓄積し続ける行為に他ならない。本稿では、レガシーシステムを延命させるための、QueryDefの「循環再利用」と「メモリ管理」の極限解を提示する。
—
1. なぜ「動的生成」がAccessを殺すのか
`CurrentDb.CreateQueryDef(“tempQuery”, strSQL)` を実行するたび、Accessはエンジン内部で新しいオブジェクト定義をメタデータとして書き込む。これを削除(`DeleteObject`)しても、物理的な領域(空き領域)は断片化し、最終的に「最適化(Compact)」なしでは修復不可能な肥大化を招く。
我々アーキテクトが目指すべきは、「QueryDefを物理的に生成せず、固定された名前のオブジェクトを更新(上書き)し続ける」という設計思想である。
—
2. QueryDef循環再利用のアーキテクチャ
以下のコードは、単一のQueryDefをキャッシュとして使い回すためのテンプレートだ。これを使うことで、MSysObjectsの肥大化を完璧に封じ込めることができる。
‘ @description: QueryDefを物理生成せず、既存オブジェクトを再利用する汎用プロシージャ
‘ @param strQueryName: 再利用するQueryDefの名前(固定)
‘ @param strSQL: 更新するSQL文
Public Sub UpdateDynamicQuery(ByVal strQueryName As String, ByVal strSQL As String)
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Set db = CurrentDb
‘ エラーハンドリング:存在しない場合のみ生成する
On Error Resume Next
Set qdf = db.QueryDefs(strQueryName)
If Err.Number <> 0 Then
Set qdf = db.CreateQueryDef(strQueryName, strSQL)
End If
On Error GoTo 0
‘ SQLプロパティを更新(物理再生成は行わない)
qdf.SQL = strSQL
‘ クリーンアップ:オブジェクト参照を明示的に解放
Set qdf = Nothing
Set db = Nothing
End Sub
—
3. パラメータークエリによる実行計画の最適化
SQL文を文字列結合で構築するのは、SQLインジェクションのリスクだけでなく、Jet/ACEエンジンによる「実行計画のキャッシュ」を阻害する。可能であれば、QueryDefの`Parameters`コレクションを利用せよ。
‘ @description: パラメータークエリを実行し、実行計画の再利用を促す
Public Sub ExecuteParamQuery(ByVal paramValue As String)
Dim qdf As DAO.QueryDef
Set qdf = CurrentDb.QueryDefs(“CachedQuery”)
‘ パラメーター値をセットし、型安全性を確保
qdf.Parameters(“[target_id]”) = paramValue
‘ 明示的にExecuteを実行(dbFailOnErrorでトランザクション整合性を保つ)
qdf.Execute dbFailOnError
Set qdf = Nothing
End Sub
—
4. 極限のメモリ最適化:Windows APIによる強制解放
Accessのガベージコレクションは優秀とは言えない。特に大規模なバッチ処理を行う際、メモリリークは致命的だ。`Set obj = Nothing`だけでは不十分なケースでは、プロセスの空きメモリをAPIで強制的に解放する手法が有効だ。
If VBA7 Then
Private Declare PtrSafe Function SetProcessWorkingSetSize Lib “kernel32” _
(ByVal hProcess As LongPtr, ByVal dwMinimumWorkingSetSize As LongPtr, _
ByVal dwMaximumWorkingSetSize As LongPtr) As Long
Private Declare PtrSafe Function GetCurrentProcess Lib “kernel32” () As LongPtr
End If
‘ メモリ断片化を防ぐための強制解放メソッド
Public Sub FlushMemory()
Call SetProcessWorkingSetSize(GetCurrentProcess(), -1, -1)
End Sub
※長時間実行するバッチのループの最後でこの`FlushMemory`を呼び出すことで、OS側のワーキングセットが最適化され、パフォーマンスの劣化を大幅に抑止できる。
—
5. 結論:アーキテクトとしての矜持
Accessを「手軽なツール」と見なすか、「堅牢な小規模基幹システム」と見なすかは、このQueryDefの管理一つで決まる。
1. 名前付きQueryDefを使い回せ。
2. 文字列結合SQLではなく、パラメータークエリを使え。
3. オブジェクト参照を必ずNothingで解放せよ。
これらを徹底すれば、あなたのシステムは数年単位で安定稼働する。肥大化という「腐敗」からシステムを守り抜くことこそが、我々アーキテクトに課せられた責務である。技術は細部に宿る。妥協なき設計を続けよ。
