Accessを蝕む「静的クエリ肥大化の呪縛」:QueryDefの動的ライフサイクル管理とメモリ最適化戦略
Access VBAによるエンタープライズシステムの開発現場において、最も見過ごされ、そしてシステムを静かに死に至らしめる癌(がん)がある。
それが、「保存済みクエリ(QueryDef)の乱造による、データベースファイルの肥大化とシステム資源の枯渇」だ。
画面の操作やバッチ処理の裏側で、動的な条件変更のたびに `CurrentDb.CreateQueryDef` を行い、それを永続的なオブジェクトとしてシステムカタログ(MSysObjects)に刻み込み続けていないだろうか?
あるいは、「一時的な処理だから」と、削除処理を怠ったクエリの残骸が、`.accdb` の内部で無数のゴミ領域(断片化)を生成し続けている現実を見逃していないだろうか。
本稿では、レガシーAccessアーキテクチャの限界を突破し、数百万レコードを扱う過酷な環境下でもパフォーマンスとメモリフットプリントを極限まで最適化するための、QueryDefの動的ライフサイクル管理の極意を授ける。
—
1. なぜ保存済みクエリの動的生成は「悪」なのか?
Accessのデータベースエンジン(ACE / Jet)は、クエリが作成されるたびに、そのSQL文を解析し、実行計画を内部のシステムテーブルに書き込む。
これがデザインビューであらかじめ作成された「静的クエリ(永続QueryDef)」であれば、エンジン側も最適化の恩恵を受けられる。
しかし、VBAから動的に生成されたクエリを、削除せずにそのまま放置、あるいは毎回新規作成と削除を繰り返した場合、以下の致命的な弊害が発生する。
① システムカタログ(MSysObjects)の肥大化とメタデータ破損
VBAで名前を指定して `CreateQueryDef` を実行すると、それはデータベースの永続コレクションに登録される。
Accessは、オブジェクトの生成と削除を繰り返すと、内部ストレージの領域再利用が追いつかず、「使われていない領域(無駄なバイナリ)」をファイル内に蓄積していく。結果として、数MBのロジックであるにもかかわらず、数GBにまでファイルサイズが膨れ上がり、最終的にJetエンジンが破損(Corruption)するトリガーとなる。
② パフォーマンスの劣化
`CurrentDb.QueryDefs` のコレクションは、参照されるたびに内部インデックスの走査やメモリ割り当てが発生する。不要なQueryDefが数千件も登録された状態での `CurrentDb.Refresh` やスキーマ走査は、システム全体のオーバーヘッドを跳ね上がらせる。
—
2. 究極の解:名前なき「一時クエリ(Temporary QueryDef)」のライフサイクル管理
この問題を根絶する唯一にして最強の手段が、「名前を持たない(Name引数に長さ0の文字列 `””` を渡した)QueryDef」の生成と、明示的なオブジェクト解放(メモリパージ)である。
名前を持たないQueryDefは、システムカタログに永続登録されない。メモリ(RAM)上のみ、あるいは一時的な作業領域でのみ生存し、オブジェクト変数がスコープを抜ける、あるいは `.Close` された瞬間に、システムリソースから綺麗に消え去る。
以下に、実務の最前線で使用すべき、メモリリークを1バイトたりとも許さない堅牢な動的クエリ実行モジュールを提示する。
実装コード:安全かつ高速な一時クエリ実行エンジン
Option Compare Database
Option Explicit
‘ ==============================================================================
‘ 致命的なメモリ肥大化を防ぐ、一時QueryDefによる安全な動的SQL実行エンジン
‘ ==============================================================================
Public Sub ExecuteDynamicQuery_Safe(ByVal sqlText As String, Optional ByVal paramName As String, Optional ByVal paramValue As Variant)
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim rst As DAO.Recordset
‘ CurrentDbを直接叩かず、変数に受けてスコープ単位で明示的に解放する
‘ (CurrentDbの連続呼び出しは新たなセッションを生成するためメモリリークの温床となる)
Set db = CurrentDb
On Error GoTo ErrorHandler
‘ 【極意】Name引数に空文字列(””)を指定することで、システムカタログに書き込まれない
‘ 「一時QueryDef」をメモリ上にのみインスタンス化する。
Set qdf = db.CreateQueryDef(“”, sqlText)
‘ パラメーターが指定されている場合のバインド処理(SQLインジェクション対策の極み)
If Not IsMissing(paramName) And paramName <> “” Then
If qdf.Parameters.Item(paramName).Type <> dbDate And IsDate(paramValue) Then
qdf.Parameters(paramName).Value = CDate(paramValue)
Else
qdf.Parameters(paramName).Value = paramValue
End If
End If
‘ クエリの実行(アクションクエリの場合)
‘ ※SELECT文の場合は OpenRecordset に切り替えること
qdf.Execute dbFailOnError
GoTo CleanUp
ErrorHandler:
‘ 予期せぬエラー発生時、スタックトレースを維持したまま上位へスロー
Dim errDesc As String
errDesc = Err.Description
MsgBox “クエリ実行時エラー: ” & errDesc, vbCritical, “チーフアーキテクト監視システム”
Err.Raise Err.Number, “ExecuteDynamicQuery_Safe”, errDesc
CleanUp:
‘ 【重要】オブジェクトの明示的解放(ライフサイクルの強制終了)
‘ Access VBAのガベージコレクションは遅延実行されるため、自ら確実に破棄する。
If Not qdf Is Nothing Then
qdf.Close
Set qdf = Nothing
End If
If Not db Is Nothing Then
Set db = Nothing
End If
End Sub
—
3. シニアエンジニアが知るべき「裏の仕組み」とWindows APIによる最適化
前述のコードで基本構造は完璧だが、極限のパフォーマンスを求めるシステムでは、Accessの裏側でうごめくCOMコンポーネントの挙動と、Windows OSのメモリ管理の特性を理解しなければならない。
DAOセッションと `CurrentDb` の罠
多くの初学者は、コード内のあちこちで `CurrentDb.Execute …` と記述する。
これは極めて危険である。`CurrentDb` は呼び出されるたびに新しい DAO.Database オブジェクトをメモリ上に生成する。これをループ内で多用しようものなら、瞬く間にメモリ空間が圧迫され、Access特有の「リソースが不足しています」エラー(Error 3035)を引き起こす。
【鉄則】
データベースへの参照は必ず1つの変数(`Dim db As DAO.Database`)に保持し、処理が終わったら即座に `Set db = Nothing` で解放し、ADO/DAOのキャッシュをフラッシュすること。
肥大化したファイルの外科手術(CompactRepair)
すでに保存済みクエリの乱造によって膨れ上がってしまった `.accdb` を救うには、VBAのコード内、あるいはバッチ処理から `DBEngine.CompactDatabase` を実行するルーチンを組み込む必要がある。
Public Sub ForceCompactDatabase(ByVal targetPath As String)
Dim tempPath As String
tempPath = Left(targetPath, InStrRev(targetPath, “\”)) & “temp_” & Dir(targetPath)
On Error GoTo CompErr
‘ 排他制御下で最適化(Compact)を実行し、肥大化したゴミ領域を完全に削ぎ落とす
DBEngine.CompactDatabase targetPath, tempPath
‘ ファイルの差し替え
Kill targetPath
Name tempPath As targetPath
MsgBox “データベースの物理最適化が完了しました。”, vbInformation
Exit Sub
CompErr:
MsgBox “最適化失敗: ” & Err.Description, vbCritical
If Dir(tempPath) <> “” Then Kill tempPath
End Sub
—
4. 結言:技術の本質を見極める者へ
「動的SQLを使うからファイルが太っても仕方ない」「動いたからいいじゃないか」——そのような妥協は、プロフェッショナルの仕事ではない。
Accessは、正しくアーキテクチャを理解し、オブジェクトの生成と消滅(ライフサイクル)を完全にコントロール下におくことで、堅牢かつ高速なエンタープライズ・データベース・プラットフォームへと変貌する。
システムカタログを汚さず、メモリを美しく解放し、不要なゴミを残さない。
この「一時クエリの哲学」をあなたのコードベースにインストールした瞬間から、Accessの挙動は見違えるほど軽快になるはずだ。
妥協なきコードを書け。それが、真のプロフェッショナルエンジニアの流儀である。
