【入門編】DAO.RecordsetのOpenRecordsetでハマる「カーソルタイプ」の最適解 – Access VBA解析バイブル

スポンサーリンク

こんにちは!Access VBAでの開発、日々お疲れ様です。
マクロの記録から一歩踏み出し、「自分でコードを書いてシステムをコントロールしたい!」という熱意、素晴らしいですね。

今回は、Access VBAの現場で「知っているか・知らないか」でシステムの生死が分かれる、非常に重要なテーマを取り上げます。それが、`DAO.Recordset` の `OpenRecordset` メソッドにおける「カーソルタイプの選択(`dbOpenDynaset` vs `dbOpenSnapshot`)」です。

ここをクリアすれば、あなたも「ただ動くだけのコード」から卒業し、「パフォーマンスとメモリの限界を知るスマートなエンジニア」への階段を確実に登ることができますよ。それでは、一緒に本質を紐解いていきましょう!

1. なぜ「OpenRecordset」でAccessがフリーズするのか?

VBAでデータを操作するとき、テーブルやクエリを開くために `CurrentDb.OpenRecordset` を使いますよね。
初学者の頃は、とりあえず以下のように書いてしまいがちです。

‘ よくある(けれど注意が必要な)書き方
Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset(“T_大量データ”, dbOpenDynaset)

もしこの「`T_大量データ`」が、数万件〜数十万件もある巨大なテーブルだったらどうなるでしょうか?
パソコンのファンが急に回り出し、画面がフリーズしたようにピクリとも動かなくなる……。そんな恐怖体験をしたことはありませんか?

この原因は、VBAのコードの書き間違いではなく、「データベースの裏側で何が起きているか(カーソルタイプの性質)」を理解していないことにあります。

2. カーソルタイプの本質:Dynaset と Snapshot の違い

`OpenRecordset` でデータを取得する際、私たちは主に次の2つのモード(カーソルタイプ)のどちらかを指定します。

1. `dbOpenDynaset`(ダイナセット):動的・更新可能な双方向カーソル
2. `dbOpenSnapshot`(スナップショット):静的・読み取り専用のコピー

この2つの違いを、身近な例でイメージしてみましょう。

【イメージ解説】

  • `dbOpenDynaset` は「監視カメラ付きのライブ中継」

テーブルのデータをリアルタイムで見つめ続けます。他のユーザーがデータを書き換えたら、自分の手元のデータもリアルタイムで変わります。「データを更新・追加・削除したい」ならこれ一択ですが、裏で常にデータベースと通信し、変更を監視するための莫大なメモリと処理コスト(オーバヘッド)を消費します。

  • `dbOpenSnapshot` は「その瞬間の写真(記念撮影)」

コードを実行した瞬間のデータを「ペロッ」と切り取って、メモリ上に固定化します。いわば静止画です。後から他の人がデータを変えても影響を受けませんし、更新もできません。しかし、監視の必要がないため、圧倒的な高速性と省メモリを誇ります。

3. 【最適解】大量データ処理ではどちらを使うべきか?

結論から言いましょう。
あなたがやろうとしていることが「データの集計」「レポート出力」「条件に合うデータの検索・参照(読み取りのみ)」であれば、答えは一つです。

絶対に `dbOpenSnapshot` を使ってください。

特に、数万件以上のレコードを扱うループ処理において `dbOpenDynaset` を使うのは、アクセスのデータベースエンジン(ACE)に対して「わざと重い負荷をかけてくれ」と言っているようなものです。

パフォーマンス比較の目安

| 項目 | `dbOpenDynaset` | `dbOpenSnapshot` |
| :— | :— | :— |
| 主な用途 | データの追加・更新・削除 | データの参照・集計・出力 |
| メモリ消費 | 多大(変更追跡のため) | 最小限(静的なコピー) |
| 処理速度 | 遅い | 圧倒的に速い |
| 他者の変更 | リアルタイムで反映される | 反映されない(取得時のまま) |

4. 実践!フリーズを回避するスマートなコード例

それでは、実務でそのまま使える安全かつ高速なコードの書き方を見てみましょう。
ここでは、「数万件ある売上データから、特定の条件に合致する件数を高速に集計する」というシーンを想定します。

Public Sub AnalyzeSalesData()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim sql As String
Dim targetCount As Long

‘ 現在のデータベースへの参照を効率的に取得(CurrentDbの乱用を防ぐプロの技)
Set db = CurrentDb()

‘ 抽出用SQLの組み立て
sql = “SELECT SalesAmount FROM T_HugeSales WHERE SalesYear = 2023”

‘ 【重要】読み取り専用かつ高速な Snapshot を指定する!
‘ dbReadOnly を併用することで、さらにパフォーマンスが最適化されます。
Set rs = db.OpenRecordset(sql, dbOpenSnapshot, dbReadOnly)

‘ レコードが存在するかチェック
If rs.EOF Then
MsgBox “対象データが存在しません。”, vbInformation
GoTo CleanUp
End If

‘ レコードセットの内部バッファを最後まで読み込ませる(正確なRecordCountを得るため)
rs.MoveLast
Debug.Print “総データ件数: ” & rs.RecordCount & ” 件”

‘ 先頭に戻る
rs.MoveFirst

‘ ループ処理(Snapshotなら数万件でも一瞬で処理されます)
targetCount = 0
Do Until rs.EOF
‘ ここでデータを読み取る(更新はできません)
If rs!SalesAmount > 10000 then
targetCount = targetCount + 1
End If

rs.MoveNext
Loop

MsgBox “条件に合う高額売上件数は ” & targetCount & ” 件です。”, vbInformation

CleanUp:
‘ 【鉄則】オブジェクトの解放(メモリリークを防ぐ)
If Not rs Is Nothing Then rs.Close: Set rs = Nothing
If Not db Is Nothing Then Set db = Nothing

End Sub

プロのエンジニアからのワンポイントアドバイス

上記のコードには、実務で絶対に外せない2つの「お作法」が組み込まれています。

1. `CurrentDb` を変数に受けていること
`CurrentDb.OpenRecordset` を何度も呼び出すと、その都度データベースへの重い接続が発生します。変数 `db` に一度格納して使い回すのが、Access高速化の基本中の基本です。
2. 使い終わったら確実に `Close` し、`Nothing` を代入していること
これを怠ると、VBAがメモリ上に居座り続け、Accessファイル自体が肥大化・破損する原因になります。

まとめ:ここをクリアすれば、Access VBAの基本はバッチリ!

今回は `OpenRecordset` におけるカーソルタイプの最適解について解説しました。

  • データを「書き換える」なら `dbOpenDynaset`
  • データを「見る・集計する」だけなら `dbOpenSnapshot`

この使い分けを意識するだけで、あなたが書くVBAコードで見違えるほどのパフォーマンス向上を実感できるはずです。「なぜそのコードを書くのか」という理由を説明できるエンジニアは、現場でとても重宝されます。

ぜひ今日の開発から、意識して `dbOpenSnapshot` を選んでみてくださいね。あなたのAccess開発ライフが、より快適で知的で楽しいものになることを応援しています!

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