【実務・中級編】ADO接続のタイムアウト制御:ネットワーク不安定時のフリーズを回避する接続設定術 – Access VBA解析バイブル

スポンサーリンク
スポンサーリンク

「応答なし」を許さない。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の責務だ。長時間かかる処理には、ステータスバーへのメッセージ表示や、プログレスバーの実装を組み合わせる。

「動くコード」を書く者は多い。しかし、「止まらないシステム」を書ける者こそが、真のエンジニアだ。

以上。

タイトルとURLをコピーしました