DAO.RecordsetとFindFirstの極限:クエリ再発行を絶滅させる高速インメモリ検索エンジンの構築
レガシーシステムの寿命を延ばすこと、そしてそのパフォーマンスを限界まで引き上げることは、我々シニアエンジニアの宿命だ。
Access VBAによる開発現場において、最も頻繁に犯される設計上の致命傷の一つが、「ユーザーが検索ボタンを押すたびにSQLクエリを発行し、フォームのレコードソースを書き換える」という愚行である。ネットワークの向こうにあるJet/ACEデータベースエンジンに対し、たかが数万件のレコード検索のためにオーバーヘッドの大きいクエリコンパイルとI/Oを強いる。これでは、マルチユーザー環境においてデータベースファイル(.accdb)の肥大化とロック競合を招き、システムは遅延の沼に沈む。
真のアーキテクトは、データを一度メモリ上にロードし、DAO(Data Access Objects)のインメモリ・レコードセット上で闘う。
今回は、`FindFirst`メソッドと`NoMatch`判定を極限までチューニングし、一瞬でターゲットを捕捉する高速検索エンジンの実装手法を解説する。
—
1. なぜ `CurrentDb.OpenRecordset` と `FindFirst` なのか
ADO(ActiveX Data Objects)の `Recordset` はクライアント/サーバー型(SQL Server等)との接続には優れているが、ローカルのACE(Access Database Engine)を極限まで高速に叩く場合、DAOこそが最適解である。オーバーヘッドが少なく、Jetエンジンとダイレクトにメモリ領域を共有するためだ。
さらに、フォームの `Me.RecordsetClone` を利用するアプローチもあるが、検索のたびにフォームのバインド状態やカレントレコードの同期に悩まされる。独立したDAOの`Snapshot`(または高速な`Dynaset`)をメモリ上に常駐させ、そこで高速インデックススキャンを行う方が、制御の確実性と処理速度の面で圧倒的に有利である。
—
2. 【実装】インメモリ検索エンジン・アーキテクチャ
以下のコードは、数万件規模のマスターテーブルから、インデックスをフル活用してミリ秒単位でレコードを特定し、非同期に近い感覚でフォームを同期させる実用エンジンである。
Option Compare Database
Option Explicit
‘ ==============================================================================
‘ モジュール名: Mdl_FastSearchEngine
‘ 概要: DAO RecordsetとFindFirstを用いた超高速インメモリ検索クラス/モジュール
‘ ==============================================================================
Private m_rstCache As DAO.Recordset
‘ ——————————————————————————
‘ 処理名: InitializeSearchEngine
‘ 概要: フォーム起動時等に一度だけ呼び出し、検索対象をメモリ上にキャッシュする
‘ ——————————————————————————
Public Sub InitializeSearchEngine()
On Error GoTo ErrorHandler
Dim db As DAO.Database
Set db = CurrentDb()
‘ 既にキャッシュが存在する場合は解放
If Not m_rstCache Is Nothing Then
m_rstCache.Close
Set m_rstCache = Nothing
End If
‘ Snapshotタイプでメモリ上に完全ロード(読み取り専用・高速化)
‘ ※あらかじめテーブル側に適切なインデックス(例: 顧客コードなど)が貼られていること
Set m_rstCache = db.OpenRecordset( _
“SELECT ID, CustomerCode, CustomerName, Phone, UpdatedAt FROM T_Customers ORDER BY CustomerCode”, _
dbOpenSnapshot, dbReadOnly)
Exit Sub
ErrorHandler:
MsgBox “検索エンジンの初期化に失敗しました: ” & Err.Description, vbCritical, “System Error”
End Sub
‘ ——————————————————————————
‘ 処理名: ExecuteFastSearch
‘ 概要: FindFirstメソッドによる超高速検索とフォームへのブックマーク同期
‘ ——————————————————————————
Public Sub ExecuteFastSearch(ByVal targetCode As String, ByRef targetForm As Access.Form)
On Error GoTo ErrorHandler
‘ キャッシュの生存確認
If m_rstCache Is Nothing Then
InitializeSearchEngine
End If
If m_rstCache.RecordCount = 0 Then Exit Sub
‘ 検索条件の構築(文字列型の場合はシングルクォーテーションで囲む)
‘ ※SQLインジェクション対策としてパラメータのサニタイジングを厳格に行うこと
Dim criteria As String
criteria = “CustomerCode = ‘” & Replace(targetCode, “‘”, “””) & “‘”
‘ 【核心】FindFirstによるメモリ上のインデックス高速スキャン
m_rstCache.FindFirst criteria
‘ NoMatch判定
If m_rstCache.NoMatch Then
MsgBox “該当するデータが見つかりませんでした。”, vbInformation, “検索結果”
Exit Sub
End If
‘ フォームのBookmarkをDAOのBookmarkと同期させ、一瞬でフォーカスを移動
‘ 注意: フォームのRecordSourceが、キャッシュ元のテーブル/クエリと同一キーを持っていること
targetForm.Bookmark = m_rstCache.Bookmark
Exit Sub
ErrorHandler:
MsgBox “検索処理中に予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “Error”
End Sub
‘ ——————————————————————————
‘ 処理名: TerminateSearchEngine
‘ 概要: アプリケーション終了時、またはフォーム破棄時に必ずメモリリークを防ぐ
‘ ——————————————————————————
Public Sub TerminateSearchEngine()
On Error Resume Next
If Not m_rstCache Is Nothing Then
m_rstCache.Close
Set m_rstCache = Nothing
End If
End Sub
—
3. シニアエンジニアが押さえるべき「極限の知見」と罠
このパターンを実務の極限環境で運用するにあたり、以下のアーキテクチャ上の注意点を看過してはならない。
① オブジェクトの明示的解放とメモリリークの根絶
VBAはガベージコレクタ(GC)を持たない。`CurrentDb` や `OpenRecordset` で生成したオブジェクトは、スコープを抜けても即座にメモリから消えない場合がある。特に `Recordset` をモジュールレベル変数(あるいはグローバル変数)として保持し続ける場合、アプリケーション終了時やフォームのアンロード時に必ず `Close` し、`Set xxx = Nothing` で参照カウントを明示的にゼロにしなければならない。これを怠ると、Accessの `.accdb` ファイル内にガチガチのメモリ断片化とリソースリークが蓄積し、数日稼働しただけで「リソース不足」エラーを引き起こす。
② テーブル側のインデックス設計(非同期の幻想を現実にする)
`FindFirst` は万能ではない。検索対象のテーブル側(今回の例では `T_Customers` の `CustomerCode`)にインデックス(Index)が定義されていない場合、`FindFirst` は結局テーブル全体を先頭から順次走査(フルスキャン)する。これではクエリ再発行と変わらない。
DAOの `FindFirst` の真価は、Jetエンジンがインデックスツリー(B-Tree)を利用して一瞬でヒット位置を特定する点にある。必ずテーブルデザインビューでインデックスが有効になっていることを確認せよ。
③ 競合制御(マルチユーザー環境の影)
インメモリでキャッシュを持つ最大のデメリットは、「他のユーザーがデータを更新した際、キャッシュが古くなる(ダーティリード状態になる)」という点だ。
完全なリアルタイム性を求めるマスターデータ(頻繁に更新されないもの)であればこのキャッシュ戦略は神速の威力を発揮するが、秒単位で他人が書き換えるトランザクションデータには向かない。運用要件に応じて、一定時間でのキャッシュ破棄(リフレッシュ)ロジックを組み込むのが、アーキテクトとしての正しい判断である。
—
結び
VBAは「おもちゃの言語」などではない。メモリの構造、オブジェクトのライフサイクル、そしてデータベースエンジンの挙動を極めれば、C#やJavaで書かれた肥大化したシステムをも凌駕する、圧倒的なレスポンスを持つ業務アプリケーションを構築できる。
クエリの再発行という無駄な贅肉を削ぎ落とし、メモリ上のDAOレコードセットを支配せよ。それこそが、真のAccessプロフェッショナルの仕事である。
