Access VBAの深淵へようこそ:Recordsetを「正しく閉じる」ことでメモリと安定性を支配する
「マクロの記録から一歩踏み出し、VBAを書き始めた。コードは動く。でも、なぜか使い続けるとAccessが重くなるし、時々『書き込み禁止』のエラーが出る……」
もしあなたがそう感じているなら、おめでとうございます。あなたは今、プログラミングの「概念」から「設計(アーキテクチャ)」へと進化する境界線に立っています。
こんにちは。今日は、Access VBAを扱う上で避けては通れない、しかし多くの初学者が「なんとなく」で済ませてしまい、後に大きな痛手(メモリリークやファイル破損)を招くテーマについてお話ししましょう。
今回のテーマは、DAOのRecordset(レコードセット)のライフサイクル管理です。
「開いたら閉じる」。当たり前のことのように聞こえますが、「いつ」「どこで」「どのような手順で」閉じるのか。この一線を理解するだけで、あなたの作るアプリケーションの堅牢性は劇的に向上します。
1. Recordsetとは「本」である
まず、イメージを固めましょう。データベース(テーブル)を「巨大な図書館」だと考えてください。
- Table(テーブル): 図書館の書棚に並んでいる本。
- Recordset(レコードセット): あなたが「読みたい」と思って、書棚から取り出し、机の上に広げた本。
Recordsetを操作するということは、本を開いてページをめくっている状態です。
ここで重要なのは、「本を広げたまま放置して、席を立たない」ということです。
もし、あなたが本を広げたまま(Recordsetを開いたまま)プロシージャを終了したり、フォームを閉じたりすると、Accessはその本が「まだ使われている」と判断します。すると、他の人がその本を書き換えようとしたときに「使用中ですよ!」というエラーが出たり、メモリという名の「机のスペース」がどんどん占領されて、Access全体の動作が重くなったりするのです。
2. 二つの「スコープ」と閉じるタイミング
Recordsetには、大きく分けて二つの「寿命(スコープ)」があります。ここを整理することが、マスターへの第一歩です。
① プロシージャ内での使い捨て(ローカルスコープ)
特定のボタンを押したときや、計算を行ったときだけ一時的に使うパターンです。
- 寿命: そのプロシージャ(`Sub` や `Function`)が終わるまで。
- 閉じるタイミング: プロシージャの最後(またはエラー発生時)。
② フォームと一緒に生きる(モジュールレベルスコープ)
フォームが開いている間中、ずっとデータを保持しておきたいパターンです。
- 寿命: フォームが開いている間ずっと。
- 閉じるタイミング: フォームが閉じるとき(`Form_Close` イベント)。
3. 実践:プロシージャ内でのベストプラクティス
まずは、最も頻繁に使う「プロシージャ内での管理」を見てみましょう。
ここで最も重要なのは、「途中でエラーが起きても、必ず閉じる」という鉄則です。
''' <summary>
''' 特定の条件に一致するデータを集計してメッセージボックスに表示する例
''' </summary>
Public Sub ShowSummaryData()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim strSql As String
' エラーハンドリングの設定(これがプロの必須技術です)
On Error GoTo ErrorHandler
Set db = CurrentDb
strSql = "SELECT ProductName, Price FROM T_Products WHERE Price > 1000"
' 1. Recordsetを開く(本を机に広げる)
Set rs = db.OpenRecordset(strSql, dbOpenSnapshot)
' データが存在するかチェック
If Not rs.EOF Then
Do Until rs.EOF
Debug.Print rs!ProductName & ": " & rs!Price
rs.MoveNext
Loop
End If
' --- ここで正常終了の準備 ---
CleanUp:
' 2. 【最重要】Recordsetを閉じて、メモリを解放する
' エラーが起きても起きなくても、必ずここを通るように設計します
If Not rs Is Nothing Then
rs.Close ' 本を閉じる
Set rs = Nothing ' 机から本を片付ける(参照を空にする)
End If
If Not db Is Nothing Then
Set db = Nothing
End If
Exit Sub
ErrorHandler:
' エラーが発生した場合の処理
MsgBox "エラーが発生しました: " & Err.Description, vbCritical
Resume CleanUp ' エラーが起きてもCleanUp(後始末)へジャンプさせる
End Sub
ここがプロの視点!
- `rs.Close` と `Set rs = Nothing` の違い:
`Close` は「本を閉じる」行為。`Set rs = Nothing` は「手に持っている本を置いて、手が空の状態にする」行為です。両方セットで行うのが、メモリを完全に解放する作法です。
- `Resume CleanUp`:
エラーが起きた瞬間にプログラムが止まってしまうと、`Close` が実行されません。エラーハンドラから後始末用のラベル(`CleanUp`)へ飛ばすことで、「どんなトラブルがあっても、必ず後始末をしてから帰る」という安全な設計になります。
4. 実践:フォームレベルでの管理
次に、フォームの変数としてRecordsetを保持する場合です。これは、フォーム上のコンボボックスの値を動的に変えたり、独自のリスト表示を行ったりする場合に使います。
Option Compare Database
Option Explicit
' モジュールレベル変数として宣言(フォームが開いている間、ずっと保持される)
Private m_rs As DAO.Recordset
''' <summary>
''' フォームが開いたときにデータをロードする
''' </summary>
Private Sub Form_Open(Cancel As Integer)
On Error GoTo ErrorHandler
' フォームが開く際にRecordsetをセット
Set m_rs = CurrentDb.OpenRecordset("SELECT * FROM T_Categories", dbOpenSnapshot)
' 何らかの処理...
Exit Sub
ErrorHandler:
MsgBox "ロード中にエラーが発生しました。"
End Sub
''' <summary>
''' フォームが閉じるときに確実に解放する
''' </summary>
Private Sub Form_Close()
' フォームが閉じるとき、メモリ上に残ったRecordsetを確実に掃除する
On Error Resume Next ' 既に閉じている場合などのエラーを無視
If Not m_rs Is Nothing Then
m_rs.Close
Set m_rs = Nothing
End If
Debug.Print "Recordsetを安全に解放しました。"
End Sub
なぜ `Form_Close` なのか?
フォームを閉じた後も Recordset がメモリに残っていると、Accessが「まだこのフォームのデータを使っているユーザーがいる」と誤解し、データベースへのロック(排他制御)が解除されない原因になります。`Form_Close` は、そのフォームが消滅する最後の瞬間、確実に後始末をするための聖域です。
5. まとめ:これだけは覚えておこう!
最後に、あなたが明日から現場で使える「黄金律」をまとめます。
| 項目 | プロシージャ内(ローカル) | フォーム内(モジュールレベル) |
|---|---|---|
| 主な用途 | 一時的な計算、データの書き出し | 画面表示、継続的なデータ参照 |
| 閉じる場所 | プロシージャの最後(`CleanUp`ラベル) | `Form_Close` イベント |
| 必須の手順 | `rs.Close` $\rightarrow$ `Set rs = Nothing` | `rs.Close` $\rightarrow$ `Set rs = Nothing` |
| 最大の敵 | エラー発生による「閉じる処理」のスキップ | フォームを閉じた後の「解放漏れ」 |
「開けたら、必ず閉じる。エラーが起きても、必ず閉じる。」
この意識を持つだけで、あなたのAccessアプリケーションは、プロが作ったような「止まらない、重くならない、壊れない」システムへと進化します。
最初は少し面倒に感じるかもしれません。でも、この「後始末の美学」を身につけたとき、あなたはもう、単なるマクロ使いではなく、真のVBAエンジニアへの階段を登りきっているはずですよ。
さあ、次のコードを書くときは、ぜひ `CleanUp` ラベルを意識してみてくださいね。応援しています!
