「応答なし」を許さない。ADO接続におけるタイムアウト制御の設計思想
業務アプリケーションを開発していると、必ずと言っていいほど直面する「ユーザーからの悲鳴」がある。
「Accessが固まった」「応答なしになって操作できなくなった」
特にSQL Serverなどの外部データベースを連携させた際、ネットワークの瞬断やサーバーの負荷増大によって、アプリケーションが数分間も無反応になる現象だ。これを放置することは、プロフェッショナルとして「未完成の製品」を納品しているのと同義である。
なぜ、あなたの作ったツールはフリーズするのか?
それは、「接続(Connection)」と「実行(Command)」のタイムアウトを混同し、ライフサイクルを制御できていないからだ。
本稿では、単なる設定値の紹介ではない。堅牢な業務システムを構築するための、ADO(ActiveX Data Objects)におけるタイムアウト制御の極限の知見を伝授する。
1. 二つのタイムアウト:概念の混同が致命的なバグを生む
多くの開発者が陥る罠は、`ConnectionTimeout` と `CommandTimeout` を同じものだと勘違いすることだ。この二つは、ネットワークにおける「フェーズ」が全く異なる。
ConnectionTimeout(接続のタイムアウト)
これは「サーバーの玄関を叩いて、中に入れるまでの時間」だ。
接続文字列(Connection String)やConnectionオブジェクトに対して設定される。ネットワークの物理的な切断、サーバーのダウン、ファイアウォールによる遮断など、「通信の確立そのもの」に失敗した場合に、いつ諦めてエラーを返すかを決定する。
- デフォルト値の罠: 多くのプロバイダーではデフォルトで15〜30秒程度に設定されている。不安定なVPN越しに接続する場合、この値が短すぎると、一瞬の揺らぎで全ユーザーの処理が停止する。
CommandTimeout(コマンドのタイムアウト)
これは「注文した料理が、いつまで経っても出てこない時の待ち時間」だ。
接続は完了しているが、SQLの実行(SELECTやUPDATE)に対してサーバーが応答を返さない場合に、いつ処理を中断するかを決定する。Commandオブジェクトのプロパティとして設定する。
- デフォルト値の罠: ADOのデフォルトは30秒だ。重い集計クエリを実行する場合、30秒でタイムアウトしてエラーを吐く。逆に、Web APIのようにリアルタイム性が求められる処理でこれを放置すると、ユーザーは「固まった」と感じる。
設計思想としての結論:
「接続できないこと」への備えと、「処理が遅いこと」への備えを、明確に分けて実装せよ。
2. プロダクション・グレードの設計:リソースの解放とエラーハンドリング
「動けばいい」というコードは、現場ではゴミ同然だ。エラーが発生した際に、Connectionオブジェクトがメモリ上に残り続け、データベースのセッションを食いつぶすケースが後を絶たない。
堅牢な設計には、以下の3要素が不可欠である。
1. Try-Catch-Finally(VBAにおける構造化エラーハンドリング)の徹底
2. オブジェクトの明示的なクローズとNothing代入
3. タイムアウト値の定数管理
以下に、私が設計現場で標準採用している、保守性と堅牢性を極めた接続モジュールの実装例を示す。
3. 【実戦コード】堅牢なADO接続管理クラス(実装例)
このコードは、単なる接続関数ではない。エラー発生時でも確実にリソースを解放し、タイムアウトを制御するための「型」である。
Option Explicit
' ======================================================================================
' Module: modDatabaseManager
' Description: SQL Server接続におけるタイムアウト制御と堅牢なエラーハンドリング
' Author: Chief Architect
' ======================================================================================
' 接続設定の定数管理(マジックナンバーを排除せよ)
Private Const CONN_TIMEOUT As Integer = 15 ' 接続確立までの待ち時間(秒)
Private Const CMD_TIMEOUT As Integer = 60 ' クエリ実行の待ち時間(秒)
''' <summary>
''' SQL Serverに対して安全にクエリを実行する
''' </summary>
''' <param name="sql">実行するSQL文</param>
''' <param name="outRecordset">結果を受け取るRecordsetオブジェクト</param>
Public Sub ExecuteSecureQuery(ByVal sql As String, ByRef outRecordset As Object)
Dim conn As ADODB.Connection
Dim cmd As ADODB.Command
' 1. オブジェクトの初期化
Set conn = New ADODB.Connection
Set cmd = New ADODB.Command
On Error GoTo ErrorHandler
' 2. 接続文字列の設定(環境に合わせて動的に生成することを推奨)
Dim connString As String
connString = "Provider=SQLOLEDB;Data Source=YOUR_SERVER;Initial Catalog=YOUR_DB;Integrated Security=SSPI;"
' 3. 接続の確立(ConnectionTimeoutの制御)
' ※ConnectionTimeoutはConnectionオブジェクトのプロパティではなく、
' 接続プロバイダーの仕様によりConnectionString内で指定するか、
' 接続開始時の挙動として制御される。
conn.ConnectionString = connString
conn.Open
' 4. コマンドオブジェクトの設定(CommandTimeoutの制御)
With cmd
.ActiveConnection = conn
.CommandText = sql
.CommandType = adCmdText
.CommandTimeout = CMD_TIMEOUT ' ここで実行タイムアウトを明示的に指定
End With
' 5. 実行
' Recordsetを生成し、結果を格納
Set outRecordset = cmd.Execute
ExitHandler:
' 6. リソースの確実な解放(これが設計の要)
' エラーが起きても起きなくても、必ずここを通る
On Error Resume Next
If Not outRecordset Is Nothing Then
' Recordsetが閉じられる必要がある場合はここで制御(状況による)
End If
If Not cmd Is Nothing Then Set cmd = Nothing
If Not conn Is Nothing Then
If conn.State = adStateOpen Then conn.Close
Set conn = Nothing
End If
Exit Sub
ErrorHandler:
' 7. エラーの格上げとログ出力
' ここでエラーの内容を解析し、ユーザーに適切なメッセージを出す
Dim errMessage As String
errMessage = "Database Error: " & Err.Number & " - " & Err.Description
' タイムアウト特有のエラーコード(例: -2147467259 / 0x80004005)を捕捉するロジックをここに実装
If Err.Number = -2147467259 Then
errMessage = "【タイムアウト】サーバーからの応答がありません。ネットワークを確認してください。"
End If
MsgBox errMessage, vbCritical, "システムエラー"
' 呼び出し元にエラーを通知するため、オブジェクトをNothingにして終了
Set outRecordset = Nothing
Resume ExitHandler
End Sub
4. アーキテクトからの最終助言
このコードを使いこなすために、以下の3点を肝に銘じておけ。
① 接続の再利用(Connection Pooling)を検討せよ
ループの中で `ExecuteSecureQuery` を呼び出すような愚かな真似はするな。ループのたびに `Open` と `Close` を繰り返せば、ネットワークのオーバーヘッドでシステムは死ぬ。接続は「セッションの開始時」に一度行い、そのConnectionオブジェクトを使い回す設計にせよ。
② トランザクションの範囲を最小限にせよ
`BeginTrans` を発行した瞬間から、データベースにはロックがかかる。その状態で `CommandTimeout` による待ち時間が発生すれば、他のユーザーまで巻き込んでシステム全体が停止する。トランザクションは「一瞬」で終わらせるものだ。
③ ユーザーへの「心理的安全性」を提供せよ
フリーズを防ぐのは技術だが、フリーズした(あるいは時間がかかっている)ことをユーザーに伝えるのはUIの責務だ。長時間かかる処理には、ステータスバーへのメッセージ表示や、プログレスバーの実装を組み合わせる。
「動くコード」を書く者は多い。しかし、「止まらないシステム」を書ける者こそが、真のエンジニアだ。
以上。
