【Access VBA極限の知見】DAO.Recordsetの真実:dbOpenSnapshotとdbOpenDynasetの境界線
こんにちは。開発プロジェクトの現場で、日々数百万レコードの巨大なAccessデータベースと格闘しているチーフアーキテクトだ。
Access VBAによる開発において、データベースとの対話の要となるのが `DAO.Recordset` である。しかし、現場のコードを見渡すと、「とりあえず `dbOpenDynaset` を使っておけば動く」「何となく `dbOpenSnapshot` が速そうだからそうしている」という、エンジニアとして最も恐ろしい“思考停止”が蔓延している。
この選択を誤ることは、アプリケーションのメモリをドブに捨て、ネットワークやローカルI/Oを圧迫し、ユーザーにストレスを与える「時限爆弾」を仕掛けると同義だ。
今回は、`dbOpenSnapshot` と `dbOpenDynaset` のメモリ消費構造、パフォーマンスのトレードオフを徹底的に解剖し、実務で絶対にバグを起こさないための「堅牢な設計指針」を授けよう。
—
1. 内部構造の理解:なぜその選択がパフォーマンスを左右するのか
まずは、この2つのタイプがAccess(正確にはJet/ACEデータベースエンジン)の内部でどのように振る舞うのか、そのライフサイクルとメモリの重みを知る必要がある。
dbOpenDynaset(ダイナセット型)
- 構造: 動的なレコードセット。ベースとなるテーブルの変更がリアルタイムに反映される。
- メモリとコスト: 非常に「重い」。レコードのポインタだけでなく、マルチユーザー環境での同時実行制御(ロッキング)や、他ユーザーによる変更を追跡するための構造を維持し続ける。
- 用途: データの「編集・追加・削除」を行う場合。
dbOpenSnapshot(スナップショット型)
- 構造: 静的なデータビュー。取得した瞬間のデータの「静止画(スナップショット)」をメモリ(またはテンポラリファイル)に展開する。
- メモリとコスト: 構築時は一時的なリソースを消費するが、一度生成されてしまえば軽量。他者によるデータの変更の影響を受けない。
- 用途: データの「参照・集計・帳票出力」のみの場合。
> 【プロの警告】
> 「後からデータを更新するかもしれないから」という理由で、全てのSELECTクエリを `dbOpenDynaset` で開くのは最悪のアンチパターンだ。10万件を超えるマスターデータやログの参照に `dbOpenDynaset` を使った瞬間、メモリリークに近いリソース枯渇を引き起こし、デスクトップアプリとしてのAccessは容易にフリーズする。
—
2. 実務における使い分けの判断基準
数千〜数十万件のレコードを扱う実務において、我々アーキテクトが遵守すべき判断基準は極めてシンプルだ。
1. 「書き込み(Update / AddNew)」が発生するか?
- Yes ➔ `dbOpenDynaset`(または `dbOpenTable` ※インデックスが効くテーブル直読みの場合)
- No ➔ 迷わず `dbOpenSnapshot` を選択する。
2. データ量はどの規模か?
- 1,00ット件未満:差は体感できないが、基本思想として参照はSnapshot。
- 1万件以上:Dynasetを使うとロック管理のオーバーヘッドが急増するため、参照系は必ずSnapshotにすること。
—
3. 【プロダクションコード】堅牢で無駄のない実装パターン
それでは、実際の現場でそのまま使える、保守性が高くエラーハンドリングを網羅したVBAコードを提示しよう。
このコードは、巨大なトランザクションデータをスナップショットで高速に読み込み、画面や別テーブルへ安全に処理する模範的な実装だ。
Option Compare Database
Option Explicit
‘ =========================================================================
‘ 務用データ処理の標準モジュール
‘ テーマ: dbOpenSnapshot を活用した安全かつ高速なデータ参照処理
‘ =========================================================================
Public Sub ProcessLargeScaleData()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim strSQL As String
Dim lngProcessCount As Long
‘ 処理時間の計測用(パフォーマンス検証の基本)
Dim dblStartTime As Double
dblStartTime = Timer
On Error GoTo ErrorHandler
‘ CurrentDbは呼び出すたびに異なるオブジェクトインスタンスを返すため
‘ 必ず変数に格納してスコープ内で使い回す(メモリリーク防止の鉄則)
Set db = CurrentDb
‘ 対象SQLの構築(例:10万件規模の売上履歴から特定条件を抽出)
strSQL = “SELECT SaleID, CustomerID, SaleDate, Amount ” & _
“FROM T_SalesHistory ” & _
“WHERE SaleDate >= #2023/01/01# ” & _
“ORDER BY SaleDate;”
‘ 【極限の知見】参照のみのため dbOpenSnapshot を明示指定。
‘ さらに dbReadOnly を併用することで、不要なロック機構を完全に排除し最速のパフォーマンスを引き出す。
Set rs = db.OpenRecordset(strSQL, dbOpenSnapshot, dbReadOnly)
‘ レコードが存在しない場合のガード
If rs.EOF Then
MsgBox “処理対象のデータが存在しません。”, vbInformation, “情報”
GoTo CleanUp
End If
‘ レコードの総数を正確に取得するためには MoveLast が必要
‘ Snapshot型であればローカル/メモリ上で完結するため高速にカウントできる
rs.MoveLast
lngProcessCount = rs.RecordCount
rs.MoveFirst
Debug.Print “処理開始件数: ” & lngProcessCount & ” 件”
‘ 画面描画の停止による高速化(UIを持つフォーム等の場合)
Application.Echo False
‘ ループ処理
Do While Not rs.EOF
‘ — ここに実際のビジネスロジックを記述 —
‘ 例: rs!Amount の値を使った集計やログ出力など
‘ Debug.Print rs!SaleID & ” : ” & rs!Amount
rs.MoveNext
Loop
Application.Echo True
MsgBox “処理が正常に完了しました。\n処理件数: ” & lngProcessCount & ” 件\n実行時間: ” & Format(Timer – dblStartTime, “0.00”) & ” 秒”, vbInformation, “完了”
CleanUp:
‘ 对象的の適切な解放(ライフサイクル管理の徹底)
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
Set db = Nothing
Exit Sub
ErrorHandler:
‘ エラー時の確実なクリーンアップ
Application.Echo True
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“エラー内容: ” & Err.Description, vbCritical, “システムエラー”
Resume CleanUp
End Sub
—
4. コードレビュー:この実装が「プロフェッショナル」である理由
上記のコードには、Access VBAで数々の修羅場をくぐり抜けてきたアーキテクトの知見が凝縮されている。
1. `CurrentDb` の変数化と使い回し
`CurrentDb.OpenRecordset` を直接ループや複数箇所で呼び出す素人がいるが、これは内部で暗黙のインスタンス生成と破棄を繰り返すため、Jetエンジンのセッションを圧迫しパフォーマンスが著しく低下する。必ず `Set db = CurrentDb` で参照を保持せよ。
2. `dbOpenSnapshot` + `dbReadOnly` の合わせ技
スナップショット型であっても、明示的に `dbReadOnly` オプションを付与することで、データベースエンジンに対して「一切の書き込みを行わない」という強い意思表示となり、余計なリソース確保をバイパスできる。
3. 徹底的なリソースの解放(ライフサイクル管理)
VBAのガベージコレクションはあてにならない。`CleanUp` ラベルを用意し、エラーが発生しようとも `Recordset` と `Database` の参照を確実に `Nothing` に解放する構造が、長時間稼働する業務システムでは絶対条件となる。
—
5. まとめ
Access VBAにおけるパフォーマンスチューニングは、派手なアルゴリズムの導入ではない。「オブジェクトの適切な型選択」と「ライフサイクルの厳格な管理」という、基礎の徹底に尽きる。
- 書き込むなら Dynaset
- 読むだけなら Snapshot
この鉄則をプロジェクトメンバー全員が共通認識として持つこと。それだけで、あなたの作る業務システムは、見違えるほど堅牢で軽快なツールへと生まれ変わるはずだ。
妥協のないコードで、真に価値のある自動化システムを構築してほしい。
