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

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

ネットワークの「沈黙」を制御せよ:ADO接続タイムアウトを掌握する堅牢な設計思想

業務自動化の現場において、我々が最も恐れるべきは「エラー」ではない。「応答なし(フリーズ)」である。

ユーザーがボタンをクリックした後、画面が白くなり、Windowsが「応答していません」と宣告する。この数秒、あるいは数分間の空白こそが、システムの信頼性を根底から破壊する。特に、ネットワーク越しにSQL Serverや外部データベースへADO(ActiveX Data Objects)で接続する構成において、この問題は「避けられない宿命」として多くの開発者を苦しめてきた。

なぜ、君の書いたコードはフリーズするのか? なぜ、エラーハンドリングを実装しているはずなのに、ネットワークが不安定になった瞬間にアプリが死ぬのか?

その答えは、ConnectionTimeoutとCommandTimeoutの決定的な違い、そしてオブジェクトのライフサイクルに対する理解不足にある。

今回は、プロフェッショナルとして「止まらない、かつ、止まるべき時に即座に止まる」堅牢なADO実装術を伝授する。

1. 致命的な誤解:ConnectionTimeout = 全てのタイムアウト

多くの初中級者が陥る罠がある。それは、「`ConnectionTimeout` を設定すれば、通信の遅延や切断をすべて制御できる」という思い込みだ。

これは致命的な誤解である。ADOにおけるタイムアウトは、明確に二つのレイヤーに分かれている。

ConnectionTimeout(接続のタイムアウト)

これは、`Connection.Open` メソッドが呼び出された瞬間から、「データベースとの握手(ハンドシェイク)が完了するまで」の猶予時間だ。

  • 対象: サーバーが見つからない、IPアドレスが間違っている、ポートが閉じている等の「入り口」での問題。
  • 役割: 接続できない場合に、いつまでも待ち続けてアプリを固まらせるのを防ぐ。

CommandTimeout(コマンドのタイムアウト)

接続が確立された後、`Command.Execute` を呼び出してクエリを実行してから、「結果が返ってくるまで」の猶予時間だ。

  • 対象: ネットワークの瞬断、サーバー側の重い処理、大量データの転送遅延等の「実行中」の問題。
  • 役割: クエリ実行中にネットワークが不安定になり、応答が途絶えた際に、いつまでも待ち続けるのを防ぐ。

「接続はできたが、その後の通信が死んだ」場合、`ConnectionTimeout` は一切役に立たない。 この違いを理解していない設計は、実務では「時限爆弾」でしかない。

2. 堅牢なアーキテクチャ:タイムアウトを制御する実装パターン

では、どのように実装すべきか。単にプロパティに数値を代入するだけでは不十分だ。
実務レベルでは、「接続の確立」と「コマンドの実行」を分離し、それぞれに適切なガードレールを敷く必要がある。

以下に、プロダクション環境でそのまま利用可能な、クラスモジュールまたは標準モジュールとして組み込める堅牢なコード例を示す。

Option Explicit

' ======================================================================================
' Module: modDatabaseCore
' Description: ネットワーク不安定時でもフリーズを回避する、堅牢なADO接続・実行エンジン
' ======================================================================================

Public Sub ExecuteSafeQuery()
    Dim cn As ADODB.Connection
    Dim cmd As ADODB.Command
    Dim rs As ADODB.Recordset
    
    ' 設定値(環境に合わせて最適化すること)
    Const CONN_TIMEOUT As Integer = 15 ' 接続待ち(秒): ネットワークの遅延を考慮
    Const CMD_TIMEOUT  As Integer = 30 ' クエリ実行待ち(秒): 重い処理を考慮
    Const CONN_STRING  As String = "Provider=SQLOLEDB;Data Source=YourServer;Initial Catalog=YourDB;Integrated Security=SSPI;"

    On Error GoTo ErrorHandler

    ' 1. Connectionオブジェクトの生成とタイムアウト設定
    Set cn = New ADODB.Connection
    cn.ConnectionTimeout = CONN_TIMEOUT
    
    Debug.Print "--- 接続開始 ---"
    ' ここでネットワークが死んでいると、CONN_TIMEOUT秒後にエラーへ飛ぶ
    cn.Open CONN_STRING
    Debug.Print "接続成功"

    ' 2. Commandオブジェクトの生成とタイムアウト設定
    Set cmd = New ADODB.Command
    Set cmd.ActiveConnection = cn
    cmd.CommandText = "SELECT * FROM LargeTable" ' 重いクエリを想定
    cmd.CommandType = adCmdText
    
    ' 重要:実行時のタイムアウトを明示的に設定
    ' これを忘れると、実行中にネットワークが切断された際にアプリがフリーズする
    cmd.CommandTimeout = CMD_TIMEOUT

    Debug.Print "--- クエリ実行開始 ---"
    ' ここでネットワークが切断されると、CMD_TIMEOUT秒後にエラーへ飛ぶ
    Set rs = cmd.Execute

    ' 3. 結果の処理
    Do While Not rs.EOF
        ' 処理ロジック
        Debug.Print rs.Fields(0).Value
        rs.MoveNext
    Loop

CleanUp:
    ' オブジェクトの解放(ライフサイクル管理の鉄則)
    On Error Resume Next
    If Not rs Is Nothing Then If rs.State = adStateOpen Then rs.Close: Set rs = Nothing
    If Not cmd Is Nothing Then Set cmd = Nothing
    If Not cn Is Nothing Then If cn.State = adStateOpen Then cn.Close: Set cn = Nothing
    Debug.Print "--- クリーンアップ完了 ---"
    Exit Sub

ErrorHandler:
    ' エラー内容に応じたロジックの分岐
    ' ADOエラー番号(0x80040E21等)を解析することで、より詳細な判定が可能
    Select Case Err.Number
        Case 1: ' 一時的な接続エラーやタイムアウトを想定
            MsgBox "通信タイムアウトが発生しました。ネットワーク環境を確認してください。", vbCritical, "通信エラー"
        Case Else
            MsgBox "予期せぬエラーが発生しました: " & Err.Description, vbCritical, "エラー: " & Err.Number
    End Select
    Resume CleanUp
End Sub

3. チーフアーキテクトの視点:さらに高みを目指すための注意点

コードを書くだけでは「中級者」だ。設計の細部まで目を配るのが「プロフェッショナル」である。

トランザクションとタイムアウトのジレンマ

もし、`BeginTrans` を発行した後にネットワークが切断され、`CommandTimeout` によってエラーが発生した場合、どうなるか?
データベース側ではトランザクションが「宙ぶらりん(orphaned transaction)」の状態になるリスクがある。

対策:

  • 必ず `On Error GoTo` によるエラーハンドリング内で、`Rollback` を明示的に実行すること。
  • ただし、接続自体が完全に断絶している場合は `Rollback` メソッド自体がエラーを吐く。そのため、`Rollback` を呼び出す際も `On Error Resume Next` で保護するか、接続状態を確認してから実行する慎重さが必要だ。

Access(Jet/ACE)エンジン特有の挙動

もし、接続先がSQL Serverではなく、共有フォルダ上の「Accessファイル(.accdb)」である場合、ADOのタイムアウト設定はより不安定になる。
ファイル共有プロトコル(SMB)の挙動に依存するため、`ConnectionTimeout` を長く設定しすぎると、OSレベルでのファイルロック待ちが発生し、VBAの制御を離れてWindows自体が固まる現象が起きる。
ファイル共有構成の場合は、タイムアウトを極力短く設定し、異常を検知したら即座にユーザーに通知して処理を中断させる設計にせよ。

ユーザーインターフェース(UI)への配慮

タイムアウトを短く設定することは、ユーザー体験を向上させる。
「30秒間フリーズして何もできない状態」よりも、「5秒で『通信エラーです』と表示される状態」の方が、ユーザーはストレスを感じない。
「待たせるな。エラーを早く出せ。それが信頼への近道だ」という原則を忘れないことだ。

まとめ

1. `ConnectionTimeout` は「ドアを開けるまで」の待ち時間。
2. `CommandTimeout` は「注文した料理が届くまで」の待ち時間。
3. ネットワーク不安定対策には、両方のプロパティを明示的に制御し、かつ適切なエラーハンドリングを組み合わせる。
4. オブジェクトの解放は、エラー発生時を含め、常に確実に行う。

これらを徹底することで、君の作成するツールは、現場の過酷なネットワーク環境下でも「決して沈黙しない」堅牢な武器へと進化する。

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