【実務・中級編】DAO.RecordsetのdbOpenSnapshotとdbOpenDynaset:メモリ消費量とパフォーマンスのトレードオフ – Access VBA解析バイブル

スポンサーリンク

【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

この鉄則をプロジェクトメンバー全員が共通認識として持つこと。それだけで、あなたの作る業務システムは、見違えるほど堅牢で軽快なツールへと生まれ変わるはずだ。

妥協のないコードで、真に価値のある自動化システムを構築してほしい。

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