接続プーリングを掌握せよ:Access/ADOにおける極限のコネクション再利用戦略
レガシーシステムの最前線に立ち続ける我々にとって、パフォーマンスチューニングとは「儀式」ではなく「生存戦略」だ。
特に、Microsoft Accessをフロントエンドに据え、バックエンドにSQL ServerなどのRDBMSを配置したクライアント・サーバーシステムにおいて、最もシステムを殺すボトルネックは何か?
それは、「無駄なコネクションのOpenとCloseの頻発」である。
今回は、DAOとADO(ActiveX Data Objects)のアーキテクチャの本質に踏り込み、ADOの接続プーリング(Connection Pooling)のメカニズムを完全にハックし、アプリケーション全体でコネクションを静的に支配・再利用する極限の設計論を解説する。
—
1. DAOとADOの決定的な違いと「接続の重み」
Access VBAの初期設定ではDAO(Data Access Objects)が標準だが、外部RDBMS(SQL Server, Oracle等)との連携や高度な非接続型アーキテクチャを語る上で、ADOの採用は不可避となる。
DAOはAccessのエンジン(Jet / ACE)と密結合しており、ローカル操作においては神速のパフォーマンスを発揮する。しかし、ODBC経由で外部サーバーを叩く場合、その内部挙動はブラックボックスに包まれ、コネクションのライフサイクルを細密に制御することはできない。
一方、ADOはOLE DBプロバイダを介してCOMコンポーネントとして動作する。 ここで知るべきは、ADOが提供する「接続」が、背後でOSやODBCドライバ、そしてネットワーク層のTCP/IPハンドシェイクを伴う非常に「重い」処理であるという事実だ。
[VBA Application]
↓ (Connection.Open)
[COM / OLE DB Wrapper]
↓ (Authentication & Network Handshake)
[RDBMS Server]
この往復を、ループ処理のたび、あるいは画面遷移のたびに繰り返す設計は、システムに対する犯罪行為に等しい。このオーバヘッドを消し去る唯一の手段が「接続プーリングの意識的活用」である。
—
2. ADO接続プーリングのメカニズムと「罠」
OLE DBプロバイダは、デフォルトで接続プーリングをサポートしている。
つまり、`Connection.Close` を呼び出しても、即座に物理的な切断が行われるわけではない。プール(Pool)と呼ばれるメモリ上の領域に接続が保持され、次に `Connection.Open` が呼ばれた際、同一の接続文字列であれば、物理的な再接続を行わずにプールからインスタンスが高速に再利用される。
しかし、Access VBAのシングルスレッド環境において、このプーリングを「なんとなく」使っているだけでは、真のパフォーマンスを引き出すことはできない。
罠:不適切なオブジェクトスコープによるプールの破綻
プロシージャレベルで `Dim cn As New ADODB.Connection` を行い、処理のたびに `Set cn = Nothing` で完全に参照を断ち切るコードを量産していないか?
これでは、COMの参照カウンタがゼロになり、プロバイダがプールを維持しているつもりでも、VBAランタイムとCOMの境界で無駄なインスタンス生成・破棄のコストが発生し、プーリングの恩恵が目減りする。さらに、接続の解放漏れ(リソースリーク)が起きれば、あっという間にRDBMS側のコネクション上限に達し、システムは沈黙する。
—
3. 実装:アプリケーション・ライフサイクルを支配する「シングルトン・コネクション管理クラス」
ここからが本題だ。
Accessアプリケーションの起動から終了まで生存し、コネクションを安全に、かつ枯渇させることなく管理する「コネクションマネージャー」をVBAのクラスモジュール(Class Module)として実装する。
以下のコードは、エラーハンドリング、接続状態の監視、そして明示的なメモリ最適化を極限まで突き詰めたプロダクション品質のアーキテクチャである。
クラスモジュール名:`CConnectionManager`
Option Explicit
‘ =========================================================================
‘ クラス名: CConnectionManager
‘ 概要: アプリケーション全体でADOコネクションを統括・再利用するシングルトンマネージャー
‘ =========================================================================
Private m_Connection As ADODB.Connection
Private Const CONNECTION_TIMEOUT As Long = 15
Private Const PROVIDER_STRING As String = “Provider=SQLOLEDB;Server=192.168.1.50;Database=EnterpriseDB;Uid=AppUser;Pwd=SecurePassword;”
‘ イニシャライザ(インスタンス生成時)
Private Sub Class_Initialize()
‘ ここではあえて即時Openせず、遅延初期化(Lazy Initialization)を採用する
End Sub
‘ ターミネータ(インスタンス破棄時:アプリケーション終了と同時に必ず呼ばれる)
Private Sub Class_Terminate()
Call TerminateConnection
End Sub
‘ ————————————————————————-
‘ コネクションの取得(常に単一の物理接続を再利用する)
‘ ————————————————————————-
Public Function GetConnection() As ADODB.Connection
On Error GoTo ErrorHandler
If m_Connection Is Nothing Then
Set m_Connection = New ADODB.Connection
m_Connection.ConnectionTimeout = CONNECTION_TIMEOUT
m_Connection.ConnectionString = PROVIDER_STRING
m_Connection.Open
Else
‘ 接続が何らかの理由で切断されている(State = adStateClosed)場合の自己修復
If m_Connection.State = adStateClosed Then
m_Connection.Open
End If
End If
Set GetConnection = m_Connection
Exit Function
ErrorHandler:
‘ 致命的な接続エラーのログ出力・ハンドリング
MsgBox “データベース接続に失敗しました: ” & Err.Description, vbCritical, “致命的エラー”
Set GetConnection = Nothing
End Function
‘ ————————————————————————-
‘ 接続の明示的な切断(アプリケーション終了時のみコールすること)
‘ ————————————————————————-
Public Sub TerminateConnection()
On Error Resume Next
If Not m_Connection Is Nothing Then
If m_Connection.State <> adStateClosed Then
m_Connection.Close
End If
Set m_Connection = Nothing
End If
On Error GoTo 0
End Sub
呼び出し側の実装パターン
ビジネスロジックやフォームのイベントからは、上記のマネージャーを介してコマンドやレコードセットを構築する。絶対に個別のプロシージャで `Connection.Open` / `Close` を乱発してはならない。
Public Sub ExecuteBusinessLogic()
Dim connMgr As CConnectionManager
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
‘ ※本来はモジュールレベル変数やフォームのライフサイクルでインスタンスを保持すべきだが、
‘ ここでは解説のため局所生成している(ただしマネージャー内部のConnectionは維持される)
Set connMgr = New CConnectionManager
Set cn = connMgr.GetConnection()
If cn is Nothing Then Exit Sub
Set rs = New ADODB.Recordset
‘ 再利用されたコネクション上で高速にクエリを実行
rs.Open “SELECT FROM M_Employee WHERE Retired = 0”, cn, adOpenForwardOnly, adLockReadOnly
‘ データの処理…
Do While Not rs.EOF
‘ Debug.Print rs!EmployeeName
rs.MoveNext
Loop
‘ レコードセットは都度確実に破棄(ADOのメモリ管理の鉄則)
rs.Close
Set rs = Nothing
‘ 注意: ここで cn.Close は呼ばない! コネクションはプール維持のためマネージャーに委譲する。
Set connMgr = Nothing
End Sub
—
4. チーフアーキテクトからの警句:メモリ最適化とリソース管理の極意
このアーキテクチャを運用する上で、シニアエンジニアとして絶対に押さえておかなければならない「破壊的罠」が2点ある。
1. レコードセットとコマンドの解放漏れ
コネクションがプールされて再利用されるということは、万が一レコードセット(`ADODB.Recordset`)やコマンド(`ADODB.Command`)のメモリリークを起こした場合、それがアプリケーションの稼働時間に比例して蓄積し、Accessのプロセス(MSACCESS.EXE)を確実にお祭り状態(メモリバースト)に導く。
`rs.Close` と `Set rs = Nothing` のセットは、宗教的義務としてコードに刻み込め。
2. トランザクション境界の制御
コネクションをアプリケーション全体で共有(シングルトン化)する場合の最大の副作用は、「ある処理で行ったトランザクションが、別の非同期的な処理に影響を与えるリスク」である。
トランザクション(`cn.BeginTrans`)を使用する場合は、必ず同一プロシージャ内で完結させ、例外発生時には確実に `RollbackTrans` を叩く構造を強制すること。共有コネクション上でトランザクションを張りっぱなしにすることは、データベース全体をロックアウトする凶行に等しい。
総括
Access VBAという、一見すると「レガシーでオモチャのような環境」であっても、その底流にあるCOM、OLE DB、そしてRDBMSの仕組みを正しく理解し、アーキテクチャを設計すれば、企業基幹に耐えうる堅牢かつ超高速なシステムへと昇華させることが可能だ。
「毎回繋いで切る」という素朴な実装から脱却し、コネクションのライフサイクルをあなたの意志で完全に掌握せよ。それこそが、真のプロフェッショナルエンジニアの仕事である。
