ネットワークの「沈黙」を制御せよ: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. オブジェクトの解放は、エラー発生時を含め、常に確実に行う。
これらを徹底することで、君の作成するツールは、現場の過酷なネットワーク環境下でも「決して沈黙しない」堅牢な武器へと進化する。
