DAOのRecordsetCloneを極限まで使い倒せ:フォーム操作のパフォーマンスと安全性における唯一解
レガシーシステムの呪縛、そして肥大化したAccessデータベースの闇。
現場のシニアエンジニアやシステム管理者であれば、一度は「フォームの検索処理が遅い」「フィルターをかけた瞬間に画面が凍りつく」という壁に直面したことがあるはずだ。
フォームの `.Filter` プロパティや `.RecordSource` を直接書き換える愚行は、今すぐやめるべきだ。Jet/ACEデータベースエンジンのオプティマイザを無駄に走らせ、UIスレッドをブロックし、最悪の場合はカレントレコードのロストや予期せぬエラーを引き起こす。
Access VBAの真髄を知る者にとって、フォーム操作の高速化と安全性の確保における「鍵」は、`RecordsetClone` 以外にあり得ない。
今回は、DAOの `RecordsetClone` を用い、メモリ管理の極限まで最適化したフォーム制御のアーキテクチャを解説する。
—
1. なぜ RecordsetClone なのか?(アーキテクチャの核心)
Accessフォームを開いたとき、Accessは裏側でテーブルまたはクエリに基づいたDAO(またはADO)のRecordsetインスタンスをメモリ上に展開している。
フォーム上の各コントロール(テキストボックスなど)は、この「フォーム専用のRecordset(カレントレコードポインタ)」に強く結びついている。
ここで `Me.RecordsetClone` を呼び出すとどうなるか?
これは、フォームが現在保持しているレコードセットの「クローン(複製)」、すなわち「同じデータソースを参照しながらも、独立したカレントレコードポインタを持つ新しいRecordsetオブジェクトへの参照」を瞬時に取得する機能である。
DAOとADOの混同による罠
前提として、AccessフォームのネイティブなデータソースはDAO(Jet/ACE)である。これを無理にADOのレコードセットにバインドしようとすると、COMのマーシャルオーバーヘッドやトランザクションの分離レベルの不整合により、パフォーマンスは劇的に悪化する。
フォーム操作においては、DAOの `RecordsetClone` を使うことが、エンジンアーキテクチャ上最もコストが低い。
—
2. 【実践コード】極限まで最適化された高速検索・位置同期ルーチン
以下のコードは、巨大なマスターテーブル(数万件規模)を抱える受注管理フォームにおいて、指定した受注IDへ瞬時にフォーカスを合わせるための実践的なプロシージャだ。
単にレコードを検索するだけでなく、メモリリークを防ぐためのオブジェクトの明示的解放、エラーハンドリング、そしてブックマーク(Bookmark)を通したUIの同期を完璧に実装している。
Option Compare Database
Option Explicit
‘ =========================================================================
‘ 処理名: フォームのRecordsetCloneを活用した高速レコード検索
‘ 概要 : UIを再描画させず、バックグラウンドのクローンセットで高速シークを行う
‘ =========================================================================
Public Sub SeekRecordByClone(ByVal TargetOrderID As Long)
‘ DAOオブジェクト変数
Dim rsClone As DAO.Recordset
‘ エラーハンドリングの要
On Error GoTo ErrorHandler
‘ 1. 画面の再描画を停止(フォームのチラつきと無駄なレイアウト計算を排除)
Application.Echo False
Me.Painting = False
‘ 2. RecordsetCloneの参照を取得
‘ ※注意: Newキーワードは不要。フォームから提供されるインスタンスを代入する。
Set rsClone = Me.RecordsetClone
‘ 3. レコードが存在するか確認
If rsClone.RecordCount = 0 Then
MsgBox “検索対象のレコードが存在しません。”, vbExclamation, “システム通知”
GoTo Finally
End If
‘ 4. インデックスを活用した高速検索 (FindFirst)
‘ ※あらかじめテーブル側の該当フィールドにインデックスが貼られていることが前提
rsClone.FindFirst “OrderID = ” & TargetOrderID
‘ 5. 検索ヒットの判定
If rsClone.NoMatch Then
MsgBox “指定された受注ID (” & TargetOrderID & “) は見つかりませんでした。”, vbInformation, “検索結果”
Else
‘ 6. フォームのBookmarkをクローンのBookmarkに同期させる
‘ これにより、フォームの表示レコードが瞬時にターゲットへジャンプする
Me.Bookmark = rsClone.Bookmark
End If
Finally:
‘ 7. オブジェクトの明示的解放(メモリ最適化の極意)
‘ RecordsetCloneで取得した参照は、必ずしもSet Nothingだけで解放しきれない
‘ メモリリークを完全に防ぐため、ローカル変数としての参照を切る
If Not rsClone Is Nothing Then
rsClone.Close
Set rsClone = Nothing
End If
‘ 8. 画面描画の復元
Me.Painting = True
Application.Echo True
Exit Sub
ErrorHandler:
‘ 異常終了時のフォールセーフ
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “致命的エラー”
Resume Finally
End Sub
—
3. シニアエンジニアが押さえるべき「3つの鉄則」
上記のコードおよび設計思想において、プロフェッショナルが絶対に外せないポイントを解説する。
① `Me.Painting = False` と `Application.Echo False` の二段構え
フォームのレコード移動やフィルター操作は、UIの再描画(Paintイベント)を伴うと著しく速度が落ちる。
特に Access のGUIスレッドはシングルスレッドで動作するため、描画処理がボトルネックになりやすい。バックグラウンドの `RecordsetClone` でシークを行い、最後に `Me.Bookmark` で一撃で同期させることで、描画コストを「1回」に抑え込む。
② `RecordsetClone` 自体は `Close` すべきか?
ここには長年議論がある。`Me.RecordsetClone` はフォームが管理するインスタンスのクローンを返すため、明示的に `Close` メソッドを呼ぶと、フォーム自体のレコードセットまで影響を受けるリスクがある(Accessのバージョンやパッチによって挙動が揺らぐことがある)。
ただし、変数としての参照を切る (`Set rsClone = Nothing`) ことは絶対の義務である。VBAのガベージコレクションやCOMの参照カウンタの仕様上、ローカル変数に残ったままプロシージャを抜けると、メモリ空間にゾンビオブジェクトが残り、やがて「リソース不足」エラーを引き起こす。
③ インデックス(Indexes)の設計とFindFirstの相乗効果
`RecordsetClone` に対する `FindFirst` メソッドは非常に強力だが、基となるテーブルやクエリの設計が雑であれば意味がない。検索キーとなるフィールドには必ずインデックスを付与しておくこと。
ジェットエンジン(ACE)がインデックススキャンを選択するか、フルテーブルスキャン(Seq Scan)になるかで、パフォーマンスは数千倍変わる。
—
4. 高度な応用:複数条件の動的フィルタリングへの転用
検索だけでなく、ユーザーが入力した複数の条件に応じてフォームを絞り込む際にも、`RecordsetClone` は真価を発揮する。
通常、`Me.Filter = “…”` と `Me.FilterOn = True` を繰り返すと、フォームのデータソースが再評価され、スクロールバーの位置がトップに戻ってしまう(カレントレコードがリセットされる)という致命的なUIの欠陥が生じる。
これを防ぐためには、以下のように `RecordsetClone` の `Filter` プロパティを利用するアーキテクチャが有効だ。
Public Sub ApplyAdvancedFilter(ByVal CustomerCode As String, ByVal StartDate As Date)
Dim rsClone As DAO.Recordset
Dim criteria As String
‘ 動的なSQL条件の構築
criteria = “CustomerCode = ‘” & CustomerCode & “‘ AND OrderDate >= #” & Format(StartDate, “yyyy/mm/dd”) & “#”
Set rsClone = Me.RecordsetClone
‘ クローン側だけにフィルターを適用
rsClone.Filter = criteria
‘ フィルター済みのクローンから、さらに新しいレコードセットビューを作成
‘ (※注意: ここで作成した派生レコードセットは必ず明示的に解放すること)
Dim rsFiltered As DAO.Recordset
Set rsFiltered = rsClone.OpenRecordset
If Not (rsFiltered.BOF And rsFiltered.EOF) Then
‘ 抽出結果が存在する場合、フォームのフィルターとして正式に適用、
‘ あるいはブックマークを転送するなどの制御を行う
Me.Filter = criteria
Me.FilterOn = True
Else
MsgBox “条件に一致するデータはありません。”, vbInformation
End If
‘ クリーンアップ
If Not rsFiltered Is Nothing Then: rsFiltered.Close: Set rsFiltered = Nothing
If Not rsClone Is Nothing Then: Set rsClone = Nothing
End Sub
—
総括:Access VBAを「制御下」に置くために
Accessは「誰でも簡単に作れる」がゆえに、誤ったコードが放置されやすい。しかし、その内実を支える DAO(Data Access Objects)は、適切に扱えば非常に堅牢で高速なデータベースアクセス機構である。
フォームの挙動にもたつきを感じたとき、安易にADOを持ち込んだり、フォームのプロパティを直接叩いて力技で解決しようとしてはならない。
「データ操作は裏側のクローン(RecordsetClone)で行い、UIの同期はブックマーク(Bookmark)を介して一瞬で完了させる」
この鉄則をマスターした瞬間から、あなたの作るAccessシステムは、レガシーの枠を超えた「プロフェッショナル・アプリケーション」へと生まれ変わる。
