Access VBAを掌握する極限の知見:`CurrentDb`の裏側と、DAO.Databaseキャッシュによる極限のパフォーマンス最適化
こんにちは。開発プロジェクトの現場で、日々数百万レコードを扱う巨大なAccess基幹システムのアーキテクチャ設計・チューニングを行っているチーフアーキテクトの私だ。
Access VBAによる開発において、君たちは何気なく`CurrentDb`を叩いていないだろうか?
「データベースを参照するんだから、必要なときにその都度`CurrentDb`を呼ぶのは当たり前だ」そう思っているなら、今すぐその認識を改めてほしい。その安易なコードの積み重ねが、君のアプリケーションを「遅い、フリーズする、使えない」代物に仕立て上げている元凶なのだ。
今回は、Accessオブジェクトモデルの核心に踏み込み、「なぜ`CurrentDb`をキャッシュすべきなのか」、そして「どう設計すればバグがなく爆速のループ処理を実現できるのか」をロジカルかつシャープに伝授しよう。
—
1. なぜ「毎回 `CurrentDb` を呼ぶコード」は遅いのか?
まずは、多くの開発者が犯すアンチパターンを見てみよう。例えば、テーブルから数千件のレコードをループ処理で更新、あるいは参照する際、以下のようなコードを書いていないか?
❌ 悪い例:ループ内で毎回 `CurrentDb` を呼び出す
‘ 【アンチパターン】ループのたびにCurrentDbを呼び出す
Dim i As Long
For i = 1 to 10000
‘ ここで毎回 DAO.Database オブジェクトが生成・破棄されている
CurrentDb.Execute “UPDATE T_Log SET Processed = True WHERE ID = ” & i, dbFailOnError
Next i
一見、何の問題もないように見える。だが、オブジェクト指向やAccessの内部構造を知る者からすれば、このコードは「アクセルを踏みながら猛烈なサイドブレーキを引いている」ようなものだ。
`CurrentDb` の正体とオーバーヘッドのメカニズム
`Application.CurrentDb` プロパティは、単なる変数への参照ではない。呼び出されるたびに、以下の重厚長大処理を水面下で行っている。
1. セッションの確立: 現在開かれているデータベースへの新しいDAOセッション(内部接続)を毎回オープンする。
2. オブジェクトのインスタンス化: メモリ上に新しい `DAO.Database` オブジェクトをアロケートする。
3. COMコンテキストの構築: Accessの内部エンジン(ACE/Jet)との間でCOMコンテキストのネゴシエーションが発生する。
4. ガベージコレクションの負荷: ループの終了とともにオブジェクトがスコープ外になり、破棄・解放処理(クリーンアップ)が走る。
これを1万回繰り返せば、CPUとメモリには凄まじい負荷がかかる。データベースの処理時間の大半が、「SQLの実行時間」ではなく「`CurrentDb`の生成と破棄のオーバーヘッド」に費やされるという本末転倒な事態に陥るのだ。
—
2. 解決策:DAO.Database変数の再利用(キャッシュ設計)
この無駄を排除する唯一にして最善の解が、「`DAO.Database` 変数をモジュールレベルまたはプロシージャの最上位で保持し、それを使い回す(キャッシュする)」という設計パターンだ。
正しいアプローチ:参照を保持し、ループ外で使い回す
‘ 【推奨パターン】CurrentDbを一度だけ変数に格納し、それを酷使する
Dim db As DAO.Database
Set db = CurrentDb ‘ ここで一度だけインスタンスを生成
Dim i As Long
For i = 1 to 10000
‘ キャッシュされたデータベース参照を使用するため、爆速で動作する
db.Execute “UPDATE T_Log SET Processed = True WHERE ID = ” & i, dbFailOnError
Next i
Set db = Nothing ‘ 最後に明示的に解放
たったこれだけのことだ。これだけで、オブジェクトの生成・破棄コストが「1回」に激減し、ループ処理は文字通り劇的に高速化する。体感速度で数倍から数十倍の差が出ることは、数万件以上のバッチ処理を組んだことがある者なら誰もが知る事実である。
—
3. 【実践】プロダクションコードで学ぶ堅牢な設計パターン
では、実際の業務アプリケーションで耐えうる、保守性と堅牢性を極限まで高めたコードを提示しよう。ここでは、エラーハンドリングとオブジェクトの確実な解放(クリーンアップ)を網羅したプロシージャの模範解答を示す。
Option Explicit
Option Compare Database
‘ ==============================================================================
‘ モジュール名: Mdl_BatchProcessor
‘ 概要 : 高速なトランザクション処理とDAOキャッシュのサンプル
‘ ==============================================================================
Public Sub ExecuteBulkUpdate()
‘ 1. 変数の宣言
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim startTime As Double
startTime = Timer ‘ 処理時間計測用
‘ エラーハンドリングの有効化
On Error GoTo ErrorHandler
‘ 2. CurrentDbをキャッシュ(DAO.Databaseとして取得)
Set db = CurrentDb
‘ 3. トランザクションの開始(一括処理の整合性と高速化のため必須)
db.BeginTrans
‘ 4. キャッシュしたdb変数からレコードセットを開く
Set rs = db.OpenRecordset(“T_TargetTable”, dbOpenDynaset)
Do Until rs.EOF
‘ — 業務ロジックの実行例 —
rs.Edit
rs!StatusFlag = 1
rs!UpdatedDate = Now()
rs!UpdatedBy = Environ(“USERNAME”)
rs!UpdateCount = Nz(rs!UpdateCount, 0) + 1
rs.Update
‘ —————————-
rs.MoveNext
Loop
‘ 5. トランザクションのコミット
db.CommitTrans
MsgBox “処理が正常に完了しました。実行時間: ” & Format(Timer – startTime, “0.00秒”), vbInformation, “成功”
GoTo Cleanup
ErrorHandler:
‘ エラー発生時はロールバックして変更を破棄
If Not db Is Nothing Then
db.Rollback
End If
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“エラー内容: ” & Err.Description, vbCritical, “システムエラー”
Cleanup:
‘ 6. オブジェクトの確実な解放(メモリリークの防止)
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
‘ db変数(CurrentDbの参照)の解放
Set db = Nothing
End Sub
このコードのアーキテクチャ的ポイント
1. `db.BeginTrans` との組み合わせ:
キャッシュした `DAO.Database` 変数を使う最大のメリットは、トランザクション制御が極めてスムーズに行える点だ。ループ内の個別クエリ実行であっても、トランザクションで囲むことでディスクI/Oのボトルネックを劇的に解消できる。
2. 確実なメモリ管理(リソースの解放):
VBAのガベージコレクションは完璧ではない。特にAccess VBAにおいて、`Recordset` や `Database` オブジェクトを解放し忘れると、メモリリークや「リソース不足」エラーを引き起こす。`Cleanup` ラベルを用意し、エラー時であっても確実に `Set xx = Nothing` を実行するイディオムを徹底している。
—
4. データベース連携・開発現場における重大な注意点
最後に、プロの現場でこのキャッシュパターンを実装する際、絶対に押さえておかなければならない「罠」について警告しておこう。
⚠️ 注意点1:フロントエンド/バックエンド分離構成での挙動
Accessシステムを「UI(フロントエンド)」と「データ(バックエンド)」に分割している場合、`CurrentDb` は常に「ローカル(フロントエンド側)のデータベース」を指している。
もしバックエンド(共有フォルダ等にある別ファイルのAccess)に対して直接SQLを発行したい場合は、`CurrentDb` ではなく `OpenDatabase` メソッドを使用し、明示的に外部DB接続をキャッシュする必要がある。
‘ 外部バックエンドDBに接続してキャッシュする場合の例
Dim dbBackEnd As DAO.Database
Set dbBackEnd = DBEngine.OpenDatabase(“\\server\share\backend_be.accdb”)
‘ 以降、dbBackEndをキャッシュとして使用
dbBackEnd.Execute “UPDATE …”, dbFailOnError
dbBackEnd.Close
Set dbBackEnd = Nothing
⚠️ 注意点2:ストライクゾーンの誤認(ADOとDAOの混同)
今回の最適化はあくまで DAO (`DAO.Database`) の文脈での話だ。近代的なADO (`ADODB.Connection`) を使用している場合は、そもそも `CurrentDb` は使用せず、コネクションオブジェクトを明示的に `Open` / `Close` する必要がある。
Access VBAの生態系においては、テーブル操作・クエリ実行はDAOが最もネイティブかつ高速に動作するため、基本方針としてはDAOによるキャッシュ設計をファーストチョイスとすべきだ。
—
総括
`CurrentDb` のキャッシュは、単なる「テクニック」ではない。Accessという枯れた、しかし奥深いプラットフォームの内部構造を理解しているプログラマーと、ネットの断片的なコードをコピペしているだけの素人を分かつ、エンジニアリングの境界線である。
君たちが開発する業務システムが、データ量の増加とともに悲鳴を上げないために。
今日からすべてのモジュールにおいて、`CurrentDb` の乱用を禁じ、DAO.Database変数のスマートなキャッシュ設計を標準実装としてほしい。
プロフェッショナルとしてのコードは、常に細部のこだわり宿る。健闘を祈る。
