Accessの深淵を覗く:QueryDefパフォーマンスプロファイラの構築と最適化の極意
Accessが「おもちゃ」だと揶揄される時代は終わった。適切に設計されたJET/ACEエンジンは、適切に扱えば数百万レコードの処理をミリ秒単位で叩き出す。しかし、多くの現場では「とりあえず動くコード」がシステムを蝕み、QueryDefの実行時間がブラックボックス化している。
今日は、あなたのAccessシステムに「監視の眼」を埋め込む。QueryDefの実行をフックし、閾値を超えた非効率なクエリを特定するための、実戦的パフォーマンスプロファイラの設計図を公開しよう。
—
1. なぜQueryDefの「実行」を監視するのか
Accessにおいて `DoCmd.OpenQuery` や `CurrentDb.Execute` を漫然と呼び出すのは、パフォーマンスのボトルネックを放置する行為だ。QueryDefはプリコンパイルされるため本来高速だが、動的SQLを `SQL` プロパティで書き換える際、実行計画が再構築されるタイミングで劇的なロスが生じる。
我々がやるべきは、QueryDefが「いつ呼ばれ、どれだけの時間を消費したか」を、既存のソースコードを極力汚さずに計測することだ。
—
2. 高精度計測のためのWindows API
VBAの `Timer` 関数は精度が低すぎる。ミリ秒単位の正確な計測には、`QueryPerformanceCounter` (QPC) を使用するのがプロトコルだ。
‘ 高精度タイマー用API
Private Declare PtrSafe Function QueryPerformanceCounter Lib “kernel32” (lpPerformanceCount As Currency) As Long
Private Declare PtrSafe Function QueryPerformanceFrequency Lib “kernel32” (lpFrequency As Currency) As Long
Private m_Frequency As Currency
‘ クラス初期化時に周波数を取得
Private Sub Class_Initialize()
QueryPerformanceFrequency m_Frequency
End Sub
‘ 経過時間をミリ秒で取得
Public Function GetElapsedMs(start As Currency, end_ As Currency) As Double
GetElapsedMs = ((end_ – start) / m_Frequency) 1000
End Function
—
3. QueryDef監視プロファイラの核
すべてのクエリ実行をラップする `QueryManager` クラスを作成する。これにより、クエリ実行のたびに計測を行い、閾値(例えば500ms)を超えた場合のみログテーブルへ書き出す仕組みを構築する。
‘ パフォーマンス監視クラス:QueryManager
Public Sub ExecuteMonitored(queryName As String, Optional parameters As Variant)
Dim qdf As DAO.QueryDef
Dim start As Currency, end_ As Currency
Dim elapsed As Double
Set qdf = CurrentDb.QueryDefs(queryName)
‘ 計測開始
QueryPerformanceCounter start
‘ クエリ実行(必要に応じてパラメータをセット)
qdf.Execute dbFailOnError
‘ 計測終了
QueryPerformanceCounter end_
elapsed = GetElapsedMs(start, end_)
‘ 閾値超過監視(500ms以上を警告)
If elapsed > 500 Then
LogPerformanceViolation queryName, elapsed
End If
‘ 明示的なオブジェクト解放(重要:VBAのGCは信用するな)
Set qdf = Nothing
End Sub
Private Sub LogPerformanceViolation(name As String, duration As Double)
‘ ログテーブルへの書き込み処理
‘ 本番環境では非同期に近い形で別テーブルへINSERTするのが定石
Debug.Print “Slow Query Detected: ” & name & ” (” & Format(duration, “0.00”) & ” ms)”
End Sub
—
4. チーフアーキテクトからの忠告:メモリと最適化の極致
上記のコードを運用するにあたり、以下の3点を徹底せよ。
1. オブジェクトの循環参照を排除せよ: `CurrentDb` をグローバル変数にキャッシュするのは便利だが、セッション終了時まで解放されないリスクを伴う。特定のスコープ内でのみ `CurrentDb` をインスタンス化し、使い終わったら即座に `Set = Nothing` するのが、大規模システムにおけるメモリリークを防ぐ鉄則だ。
2. パラメータクエリの真価: 文字列結合でSQLを作るな。必ず `qdf.Parameters` を通して値を渡せ。これにより、JETエンジンはクエリプランをキャッシュし、2回目以降の実行速度が劇的に向上する。
3. レガシー環境でのAPI呼び出し: `PtrSafe` キーワードを忘れるな。64bit Access環境への移行を考慮しないコードは、もはや負債でしかない。
—
結びに:なぜ「計測」がすべてなのか
システムが重い原因の9割は、直感的な「ここが怪しい」という思い込みの中にはない。データが蓄積され、インデックスが機能しなくなり、クエリプランが肥大化した結果、システムは静かに死んでいく。
このプロファイラを導入し、ログを蓄積せよ。データこそが、あなたのコードを「ただのプログラム」から「信頼に足るエンタープライズソリューション」へと昇華させる唯一の根拠となる。
コードを書くのではない。システムの寿命を設計するのだ。さあ、次はどのクエリを最適化する?
