【テクニカル・上級編】QueryDefの実行時間を計測し、遅いクエリを特定する「パフォーマンスプロファイラ」の自作 – Access VBA解析バイブル

スポンサーリンク

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割は、直感的な「ここが怪しい」という思い込みの中にはない。データが蓄積され、インデックスが機能しなくなり、クエリプランが肥大化した結果、システムは静かに死んでいく。

このプロファイラを導入し、ログを蓄積せよ。データこそが、あなたのコードを「ただのプログラム」から「信頼に足るエンタープライズソリューション」へと昇華させる唯一の根拠となる。

コードを書くのではない。システムの寿命を設計するのだ。さあ、次はどのクエリを最適化する?

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