こんにちは!Access VBAの世界へようこそ。
マクロの記録ボタンを押すだけの世界から一歩踏み出し、自分の手でロジックを組み立てる楽しさに目覚めた頃ではないでしょうか。
今回は、VBAでのデータ操作において避けて通れない「DAO.Recordset(レコードセット)」の核心に迫ります。
その中でも、よく使われるのに違いが曖昧になりがちな「dbOpenSnapshot」と「dbOpenDynaset」の使い分けについて、メモリやスピードの観点から徹底的に紐解いていきましょう。
ここをクリアすれば、あなたの書くプログラムは見違えるほど軽快になり、「おっ、できるな!」と周囲をうならせるエンジニアに一歩近づけますよ。それでは、一緒に本質をマスターしていきましょう!
—
1. レコードセットとは? そしてなぜ「開き方」が重要なのか
AccessでVBAからデータを扱うとき、私たちはテーブルやクエリのデータをメモリ上に持ってくる必要があります。その受け皿となるのが Recordset(レコードセット) オブジェクトです。
データを取得するとき、次のように書いていませんか?
Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset(“T_顧客”, dbOpenDynaset)
この `OpenRecordset` の第2引数に指定しているのが、「カーソルタイプ(データの開き方)」です。
ここに何を 지정するかによって、Accessが裏側で行う仕事量、つまりメモリの消費量と処理速度が劇的に変わるのです。
代表的な2つのタイプの特徴を、まずはざっくりと見てみましょう。
- `dbOpenDynaset`(ダイナセット):データの「読み取り」だけでなく「追加・更新・削除」もできる多機能型。ただし、裏で変更を監視するため、ちょっぴり重い。
- `dbOpenSnapshot`(スナップショット):データの「読み取り専用」。写真(Snapshot)のように、開いた瞬間のデータをパシャリと切り取って固定する。更新ができない代わりに、圧倒的に速くて軽量。
—
2. 【図解】ダイナセットとスナップショットの裏側の動き
なぜ、スナップショットの方が速いのでしょうか?
イメージ図で脳内バッファに焼き付けましょう。
【dbOpenDynaset(ダイナセット型)】
[VBAのプログラム] ──(変更要求)──> [ ダイナセット ]
↕ (常時監視・同期)
[ 実際のテーブル ]
※他の人がデータを変えたら、自分の手元もリアルタイムで変わるように監視コストを払う。
【dbOpenSnapshot(スナップショット型)】
[VBAのプログラム] ──(参照のみ)──> [ スナップショット(静的なコピー) ]
[ 実際のテーブル ] (※繋がりはここで断たれる)
※開いた瞬間のデータをメモリに丸め込むだけなので、監視の必要がなく爆速!
実務でVBAを書くとき、「画面に表示してユーザーにグリグリ編集してもらう」のでなければ、テーブルのデータを集計したり、ループで回してログを出力したりする処理の大部分は「読むだけ(参照のみ)」です。
読むだけなのに、わざわざ監視機能のある `Dynaset` を使うのは、例えるなら「近所のコンビニに行くだけなのに、わざわざ大型ダンプカーをエンジンかけっぱなしでチャーターする」ようなもの。燃料(メモリ)の無駄遣いになってしまいます。
—
3. 実践!どれくらい速さが違うのか?コードで検証
百聞は一見に如かず。実際にコードを書いて、その差を体感してみましょう。
以下のコードは、数万件あるデータから特定の条件に合うものを読み込んで処理する想定のサンプルです。
Sub CompareRecordsetPerformance()
Dim db As DAO.Database
Dim rsDyn As DAO.Recordset
Dim rsSnap As DAO.Recordset
Dim startTime As Double
Dim count As Long
Set db = CurrentDb
‘—————————————————-
‘ 1. Dynaset(ダイナセット)での計測
‘—————————————————-
startTime = Timer
‘ 更新可能モードで開く(デフォルト)
Set rsDyn = db.OpenRecordset(“T_巨大データ”, dbOpenDynaset)
count = 0
Do Until rsDyn.EOF
‘ 何かしらデータを読む処理(今回はカウントのみ)
count = count + 1
rsDyn.MoveNext
Loop
rsDyn.Close
Debug.Print “Dynaset 処理時間: ” & Format(Timer – startTime, “0.00秒”)
‘—————————————————-
‘ 2. Snapshot(スナップショット)での計測
‘—————————————————-
startTime = Timer
‘ 読み取り専用(スナップショット)で開く
Set rsSnap = db.OpenRecordset(“T_巨大データ”, dbOpenSnapshot)
count = 0
Do Until rsSnap.EOF
‘ 読み込む処理
count = count + 1
rsSnap.MoveNext
Loop
rsSnap.Close
Debug.Print “Snapshot処理時間: ” & Format(Timer – startTime, “0.00秒”)
‘ 後片付け
Set rsSnap = Nothing
Set rsDyn = Nothing
Set db = Nothing
End Sub
実行結果の傾向(イメージ)
- `Dynaset`:裏での変更監視コストがかかるため、ループを回し終えるまでに「おや、少し待たされるな」と感じる。
- `Snapshot`:メモリ上でシュッとデータを読み切るため、「えっ、もう終わったの?」と驚くほどスピーディーに完了する。
特にデータ件数が数千件、数万件と増えていくにつれて、この差は致命的なものになって現れます。
—
4. 陥りがちなエラーと、先輩エンジニアからのアドバイス
ここで、初心者がよくハマる「罠」をいくつかご紹介しておきます。
罠1:Snapshotを開いたあとに「書き換えよう」として怒られる
Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset(“T_顧客”, dbOpenSnapshot)
rs.Edit ‘ ←ここで実行時エラー「更新可能なレコードセットではありません。」が発生!
rs!顧客名 = “山田 太郎”
rs.Update
【解説】
スナップショットは「写真」なので、元のデータを書き換えることはできません。データを更新したい場合は必ず `dbOpenDynaset` を使いましょう。「読むときはSnapshot、書くときはDynaset」という住み分けが鉄則です。
罠2:CurrentDbの乱用によるメモリリーク
コード内で何度も `CurrentDb.OpenRecordset` を呼び出す際、変数への格納や解放をサボると、Accessの内部メモリが圧迫され、アプリがフリーズする原因になります。
【黄金の作法】
1. データベースオブジェクトは変数に受ける(`Dim db As DAO.Database: Set db = CurrentDb`)
2. 使い終わったレコードセットは必ず閉じる(`rs.Close`)
3. オブジェクト変数は `Nothing` を代入してメモリをキレイに解放する
この基本を守るだけで、Accessの安定性が劇的に向上します。
—
まとめ:今日のまとめとして
- 「データを参照して何かするだけ(集計、チェック、出力など)」の処理には、迷わず `dbOpenSnapshot` を選ぼう。
- 「データの追加・編集・削除」を行うときだけ `dbOpenDynaset` を使おう。
- 使い終わったリソース(RecordsetやDatabase)は、必ず `Close` して `Nothing` を代入する癖をつけよう。
用途に合わせた正しい道具(カーソルタイプ)を選ぶことは、プログラミングの第一歩であり、プロとアマを分かつ大きな分かれ道です。
ここをクリアしたあなたなら、もう「動くだけの遅いコード」から卒業し、「速くてスマートなコード」が書けるエンジニアの仲間入りです。ぜひ、日々の開発に取り入れてみてくださいね!
