SqlConnectionの接続タイムアウトを確認してみる

SQL Serverで単にタイムアウトと言っても接続タイムアウトとクエリタイムアウト(コマンドタイムアウト)の2つがあります。発生するタイミングが違いますし、それぞれ理解しておきたいなと思います。

まずはちょっと簡単に整理します。

接続タイムアウト

名前の通りなんですがSQL Serverへ接続を試みるときのタイムアウト。SqlConnection.Openのときに発生する場合があります。

タイムアウトまでの時間はSqlConnectionStringBuilder.ConnectTimeoutプロパティで設定します。SqlConnection.ConnectionTimeoutプロパティからは参照だけ。

クエリタイムアウト(コマンドタイムアウト

こちらも名前のとおりですがSqlCommand.ExecuteNonQueryやSqlCommand.ExecuteReaderなどのタイムアウト。去年の今ごろSqlCommandでタイムアウトのときに発生するSqlExceptionを拾って確認してみましたが、今思うとクエリタイムアウトのことですね。

ichiroku11.hatenablog.jp

タイムアウトまでの時間はSqlCommand.CommandTimeoutプロパティで設定・参照できます。

接続タイムアウトを確認してみる

てことで接続タイムアウトを確認してみようと思います。接続に失敗する原因は色々あると思いますが試しにSQL Serverを停止して次のコードを実行してみるとSqlExceptionが発生します。

var connectionString = new SqlConnectionStringBuilder {
    DataSource = "lpc:.",    // 共有メモリプロトコル
    InitialCatalog = "Test",
    IntegratedSecurity = true,
    ConnectTimeout = 1,    // 試しに1秒でタイムアウトするように
}.ToString();

var connection = new SqlConnection(connectionString);
// タイムアウトは1秒になっている
Console.WriteLine($"{nameof(connection.ConnectionTimeout)}: {connection.ConnectionTimeout}");
//ConnectionTimeout: 1

try {
    // コネクションを開こうとして1秒ほどでタイムアウト(SqlExceptionがスロー)
    connection.Open();
} catch (SqlException exception) {
    Console.WriteLine($"{nameof(SqlException)}:");
    Console.WriteLine($"{nameof(exception.Number)}: {exception.Number}");
    Console.WriteLine($"{nameof(exception.Message)}: {exception.Message}");
    //SqlException:
    //Number: 2
    //Message: SQL Server への接続を確立しているときにネットワーク関連またはインスタンス固有のエラーが発生しました。サーバーが見つからないかアクセスできません。インスタンス名が正しいこと、および SQL Server がリモート接続を許可するように構成されていることを確認してください。 (provider: Shared Memory Provider, error: 40 - SQL Server への接続を開けませんでした)
} finally {
    connection.Close();
}

コネクションプールの最大接続数を超えた場合

実はこれこの前初めて経験しました。コネクションプールの最大接続数を超えてコネクションを開こうとするとInvalidOperationExceptionが発生します。

試しに最大接続数を2にした場合、コネクションを2つ開いた状態から3つ目のコネクションを開こうとして例外が発生します。

var connectionString = new SqlConnectionStringBuilder {
    DataSource = "lpc:.",
    InitialCatalog = "Test",
    IntegratedSecurity = true,
    MaxPoolSize = 2,   // 最大接続数
    ConnectTimeout = 1,    // 1秒でタイムアウト
}.ToString();

var connection1 = new SqlConnection(connectionString);
var connection2 = new SqlConnection(connectionString);
var connection3 = new SqlConnection(connectionString);

try {
    // コネクション2つまでは開く
    connection1.Open();
    Console.WriteLine($"{nameof(connection1)}: opened");
    //connection1: opened

    connection2.Open();
    Console.WriteLine($"{nameof(connection2)}: opened");
    //connection2: opened

    // 3つめのコネクションを開こうとして1秒ほどで例外
    connection3.Open();
} catch(InvalidOperationException exception) {
    Console.WriteLine($"{nameof(InvalidOperationException)}:");
    Console.WriteLine($"{nameof(exception.Message)}: {exception.Message}");
    //InvalidOperationException:
    //Message: タイムアウトに達しました。プールから接続を取得する前にタイムアウト期間が過ぎました。プールされた接続がすべて使用中で、プール サイズの制限値に達した可能性があります。
} finally {
    connection1.Close();
    connection2.Close();
    connection3.Close();
}

参考