Accessの死角を撃つ:DAO.Recordsetの「Snapshot」か「Dynaset」か、その深淵なる最適化
Access VBAにおけるDAO(Data Access Objects)の選定は、単なるプログラミングの趣味嗜好ではない。それは、限られたメモリ空間とI/O帯域をどう支配するかという、アーキテクチャの根幹をなす決断である。
なぜ、あなたの書いたツールは、データ量が増加するにつれて「重く」なるのか。なぜ、マルチユーザー環境でデッドロックが多発するのか。その答えの多くは、`dbOpenDynaset` と `dbOpenSnapshot` の性質を正しく理解していないことにある。
本稿では、シニアエンジニアとして、この二つのタイプの「メモリ負荷」と「物理的挙動」を解剖し、極限のパフォーマンスを引き出すための知見を共有する。
—
1. dbOpenDynaset:双方向の対話、しかしコストの代償
`dbOpenDynaset` は、レコードセットを「ライブ接続」として扱う。基底のテーブルやクエリと同期を保ち、更新、削除、挿入が可能だ。
メモリの罠とロックの闇
Dynasetは、行ごとにIDを保持し、更新のたびにロックマネージャへ問い合わせを行う。
- メモリ負荷: 非常に高い。各レコードの更新可能性を担保するために、Jet/ACEエンジンは「ブックマーク」や「排他制御用のヘッダ情報」を保持し続ける。
- ネットワーク負荷: 接続先がファイルサーバー上のMDB/ACCDBであれば、レコードの移動ごとにページ単位のロックとデータ転送が発生する。
結論: 大規模なデータセットに対し、不必要にDynasetを開くのは「メモリのドブ捨て」である。
—
2. dbOpenSnapshot:静的なる高速化の極致
`dbOpenSnapshot` は、オープンした瞬間のデータセットの「静止画」をメモリまたはTempファイル上に確保する。
Snapshotの真価
- 高速性能: 更新制御のオーバーヘッドが一切ない。一度読み込めば、カーソル移動はメモリ内で完結する(データ量によるが)。
- 低負荷: 他のユーザーがデータを更新しても、自分のレコードセットには影響しない(=排他制御の競合が発生しない)。
結論: 「読み取り専用」と割り切れる処理であれば、迷わずSnapshotを選ぶべきだ。これは単なるコードの最適化ではなく、データベースエンジン全体の負荷を軽減する「社会貢献」でもある。
—
3. コードで見る「メモリ管理」の極限
ただレコードセットを開き、`Nothing` を代入するだけで満足していないだろうか。オブジェクトの解放は、OSへのメモリ返還を保証するものではない。明示的なクローズとメモリ管理の定石をここに記す。
‘ 高速な読み取り専用処理:Snapshotの利用
Public Sub ExportDataEfficiently(ByVal sql As String)
Dim db As DAO.Database
Dim rs As DAO.Recordset
‘ CurrentDbを直接参照すると内部キャッシュで重くなるため、
‘ 必要に応じて変数を介して再利用する
Set db = CurrentDb
‘ dbOpenSnapshotで高速化。さらにdbForwardOnlyを指定すると、
‘ 前方への移動のみに制限することでメモリ消費を最小化できる
Set rs = db.OpenRecordset(sql, dbOpenSnapshot, dbForwardOnly)
With rs
If Not .EOF Then
Do While Not .EOF
‘ ここにビジネスロジックを記述
Debug.Print !FieldName
.MoveNext
Loop
End If
‘ 重要なのは、Closeの直後にNothingを代入すること
‘ これによりVBAの参照カウンタが即座にデクリメントされる
.Close
End With
Set rs = Nothing
Set db = Nothing
End Sub
—
4. シニアエンジニアが知るべき「裏側の挙動」
なぜ、たまに「メモリ不足」が起きるのか
`dbOpenDynaset` で巨大なJOINクエリを叩くと、Accessは内部的に「一時的なインデックス」を作成する。これがRAMを圧迫し、Windows APIの `GlobalMemoryStatusEx` が示す空き容量を無視したかのようにクラッシュを招くことがある。
APIによるモニタリングの重要性
大規模なシステム連携を行う場合、VBAの `DoEvents` だけでは不十分だ。時にはWindows APIを呼び出し、プロセスのメモリ使用量を監視し、必要であれば `Application.CompactRepair` を自動実行させるような、堅牢な運用設計が求められる。
‘ メモリ解放を強制するヒント(Windows APIの利用)
‘ 注意: SetNothingだけでは足りない極限状況では、
‘ 内部的なメモリプールをクリーンアップするロジックが必要になることもある
Private Declare PtrSafe Sub SetProcessWorkingSetSize Lib “kernel32” _
(ByVal hProcess As LongPtr, ByVal dwMinimumWorkingSetSize As Long, _
ByVal dwMaximumWorkingSetSize As Long)
—
終わりに:技術の最適化は「責任」である
Access VBAを軽視するエンジニアが多いが、それは彼らが「制御」の楽しさを知らないからだ。
1. 書き込みが必要か? → 必要ならDynaset、不要ならSnapshot。
2. 前方移動だけで十分か? → `dbForwardOnly` を付与せよ。
3. オブジェクトの生存期間は適切か? → ローカルスコープで完結させ、即座に解放せよ。
この3つの鉄則を守るだけで、あなたのシステムは驚くほど軽快になる。レガシーだから遅いのではない。設計が、そのポテンシャルを殺しているだけなのだ。
次回の記事では、`QueryDef` を活用した「プリコンパイル済みSQLによる実行速度の最適化」について、より深く掘り下げる。引き続き、Accessの深淵を歩もう。
