Microsoft.Data.SqlClientで接続エラーを自動再試行する方法|NumberOfTriesと適用範囲

Microsoft.Data.SqlClientで接続エラーを自動再試行するには、SqlRetryLogicOptionで試行回数と待機時間を定義し、SqlConfigurableRetryFactory.CreateExponentialRetryProvider()で作成したProviderをSqlConnection.RetryLogicProviderへ割り当てます。NumberOfTries = 5は「初回実行+最大4回の再試行」であり、5回再試行する設定ではありません。([Microsoft for Developers][1])

この仕組みはConfigurable Retry Logicと呼ばれます。ただし、接続とコマンドでは設定が独立しており、SqlConnectionへProviderを設定しても、その接続から作成したSqlCommandまで自動的に再試行されるわけではありません。また、既定では無効です。([Microsoft Learn][2])

目次

Configurable Retry Logicは今回新設された機能ではない

Configurable Retry Logicは、2026年に新設された機能ではありません。

Microsoft.Data.SqlClient 3.0以降で利用でき、プレビュー期間を経てMicrosoft.Data.SqlClient 4.0でGAになった既存機能です。2026年にMicrosoft公式ブログで改めて紹介されたため新機能のように見えますが、以前から利用できます。([Microsoft for Developers][1])

重要なのは、Microsoft.Data.SqlClientを更新しただけでは再試行が始まらない点です。通常は次のいずれかにProviderを明示的に設定します。

再試行したい処理設定先
データベースへの接続開始SqlConnection.RetryLogicProvider
SQLコマンドの実行SqlCommand.RetryLogicProvider
接続とコマンドの両方それぞれ個別に設定

Providerを設定していない場合は、再試行しないProviderが使われます。既存アプリへMicrosoft.Data.SqlClientを導入しただけで、接続障害への耐性が自動的に上がるわけではありません。([Microsoft Learn][2])

System.Data.SqlClientから移行する前に確認すること

Configurable Retry Logicを利用するには、System.Data.SqlClientではなくMicrosoft.Data.SqlClientを使用します。

まず、ソリューション全体で次の文字列を検索してください。

System.Data.SqlClient

確認対象は、C#ファイルのusingだけではありません。

  • .csprojやpackages.configのパッケージ参照
  • 完全修飾名で記述されたSystem.Data.SqlClient.SqlConnection
  • 共通ライブラリが公開しているSqlConnectionやSqlCommand型
  • DIコンテナへの登録
  • サードパーティー製ライブラリの依存関係
  • テストプロジェクトや移行ツール

SDK形式のプロジェクトであれば、次のコマンドでパッケージを追加できます。

dotnet add package Microsoft.Data.SqlClient

その後、名前空間を変更します。

// 変更前
using System.Data.SqlClient;

// 変更後
using Microsoft.Data.SqlClient;

Microsoft.Data.SqlClientはSystem.Data.SqlClientと似たAPIを維持しており、多くのアプリでは移行しやすい設計です。ただし、すべてのアプリが無修正で移行できるとは限りません。特に、外部ライブラリがSystem.Data.SqlClientの型へバイナリ依存している場合や、データアクセス層の外までProvider固有型を公開している場合は修正が必要になることがあります。([Microsoft for Developers][1])

パッケージとusingを書き換えた後は、少なくとも次を確認します。

  1. ソリューション全体がビルドできる
  2. 接続文字列を読み込める
  3. 接続、検索、更新、トランザクション処理が動作する
  4. Entity FrameworkやDapperなど、利用中のライブラリとの組み合わせに問題がない
  5. 配布先で必要なランタイム依存ファイルを読み込める

SqlConnectionの接続エラーを自動再試行する実装

接続開始時の一時的なエラーを再試行する基本コードは次のとおりです。

using System;
using Microsoft.Data.SqlClient;

var options = new SqlRetryLogicOption
{
    NumberOfTries = 5,
    DeltaTime = TimeSpan.FromSeconds(1),
    MaxTimeInterval = TimeSpan.FromSeconds(20)
};

SqlRetryLogicBaseProvider retryProvider =
    SqlConfigurableRetryFactory.CreateExponentialRetryProvider(options);

using var connection = new SqlConnection(connectionString)
{
    RetryLogicProvider = retryProvider
};

await connection.OpenAsync();

処理の流れは次のようになります。

  1. SqlRetryLogicOptionで再試行条件を定義する
  2. CreateExponentialRetryProvider()でProviderを作成する
  3. SqlConnection.RetryLogicProviderへ設定する
  4. Open()またはOpenAsync()を実行する

OpenAsync()でSqlClientが再試行対象と判断した一時的エラーが発生すると、設定された回数と待機時間に従って接続が再実行されます。すべての試行に失敗した場合は、最終的に例外が呼び出し元へ返されます。([Microsoft for Developers][1])

NumberOfTriesは再試行回数ではなく総試行回数

NumberOfTriesは、最初の実行を含む総試行回数です。

設定値初回実行最大再試行回数総試行回数
11回0回1回
21回1回2回
31回2回3回
51回4回5回

したがって、再試行回数は次の式で考えられます。

最大再試行回数 = NumberOfTries - 1

「最大5回再試行したい」のであれば、設定値はNumberOfTries = 6です。NumberOfTries = 5を5回の再試行と解釈すると、障害時の待機時間やデータベースへの接続回数を誤って見積もることになります。([Microsoft Learn][3])

DeltaTimeとMaxTimeIntervalの意味

主な設定項目は次のとおりです。

設定項目意味注意点
NumberOfTries初回を含む総試行回数再試行回数ではない
DeltaTime再試行間隔の計算に使われる時間差実際の待機時間が常にこの値になるとは限らない
MaxTimeInterval1回ごとの待機時間の上限処理全体のタイムアウトではない
TransientErrors再試行対象とするエラー番号設定すると組み込み一覧を置き換える
AuthorizedSqlCondition再試行を許可するSQLの判定条件主にSqlCommandで使用する

CreateExponentialRetryProvider()では、再試行を重ねるごとに待機時間が増加します。組み込みProviderにはジッターも加えられるため、多数のクライアントが同時に再接続する集中を抑えられます。([Microsoft Learn][3])

MaxTimeIntervalは、1回の待機時間に対する上限です。接続処理全体を20秒以内に終わらせる設定ではありません。

実際の所要時間には、次の時間も含まれます。

  • 各接続処理にかかる時間
  • Connect Timeoutで待機する時間
  • 再試行間の待機時間
  • ジッターによって追加される時間
  • DNS、TLS、ネットワーク機器などの応答時間

アプリ全体の応答時間に上限がある場合は、NumberOfTriesだけでなく、接続文字列のタイムアウトやCancellationTokenを含めて設計する必要があります。

SqlConnectionとSqlCommandの再試行範囲は異なる

SqlConnection.RetryLogicProviderが対象とするのは、主に接続を開く処理です。

await connection.OpenAsync();

接続後に実行する次の処理まで、自動的に同じProviderで再試行されるわけではありません。

await command.ExecuteReaderAsync();
await command.ExecuteNonQueryAsync();
await command.ExecuteScalarAsync();

コマンド実行を再試行したい場合は、SqlCommand.RetryLogicProviderへ別途Providerを設定します。接続用Providerとコマンド用Providerは独立しているため、接続に設定したProviderがコマンドへ継承されることもありません。([Microsoft Learn][2])

障害が発生する場所必要な設定
Open()、OpenAsync()SqlConnection.RetryLogicProvider
ExecuteReader()、ExecuteNonQuery()などSqlCommand.RetryLogicProvider
接続とコマンドの両方両方にProviderを設定
トランザクション全体アプリ側でトランザクション単位の再実行を設計

接続だけを安定させたい場合は、最初からコマンド再試行まで有効にする必要はありません。まずOpenAsync()の再試行から導入した方が、二重更新などの副作用を避けやすくなります。

SqlCommandを再試行する場合は安全なSQLだけに限定する

読み取り処理など、安全に繰り返せるSQLへ限定する例は次のとおりです。

using System;
using Microsoft.Data.SqlClient;

const string retrySafeSql =
    "SELECT COUNT_BIG(*) FROM dbo.Jobs";

var commandOptions = new SqlRetryLogicOption
{
    NumberOfTries = 3,
    DeltaTime = TimeSpan.FromSeconds(1),
    MaxTimeInterval = TimeSpan.FromSeconds(5),

    AuthorizedSqlCondition = commandText =>
        string.Equals(
            commandText,
            retrySafeSql,
            StringComparison.Ordinal)
};

var commandRetryProvider =
    SqlConfigurableRetryFactory.CreateExponentialRetryProvider(
        commandOptions);

using var command = connection.CreateCommand();

command.CommandText = retrySafeSql;
command.RetryLogicProvider = commandRetryProvider;

var result = await command.ExecuteScalarAsync();

AuthorizedSqlConditionにはコマンド文字列が渡され、trueを返した場合だけ再試行が許可されます。上記は、あらかじめ安全だと確認した特定の読み取りSQLだけを対象にする例です。([Microsoft Learn][4])

単純に「SELECTで始まっていれば安全」と判定する実装は避けた方が無難です。コメント、共通テーブル式、ストアドプロシージャ、複数ステートメントなどが加わると、文字列の先頭だけでは安全性を正確に判断できないためです。

コマンド再試行を導入する際は、次の優先順位で判断します。

  1. 同じ処理を複数回実行しても結果が変わらないか
  2. 更新処理には一意キーや冪等性キーがあるか
  3. サーバー側では成功したが、クライアントが結果を受信できなかった場合に重複しないか
  4. トランザクションの一部だけを再実行して問題がないか

特にINSERTや外部通知を伴う処理は注意が必要です。SQL Server側では処理が完了しているものの、応答だけがクライアントへ届かなかった場合、同じコマンドを再実行すると二重登録につながる可能性があります。

トランザクション内のコマンドは自動再試行されない

組み込みのコマンド用Providerは、開いているトランザクション内のコマンドを自動再試行しません。

対象には、主に次の状態が含まれます。

  • SqlTransactionが設定されている
  • TransactionScopeの中で実行されている

この場合、コマンドは再試行されず1回だけ実行されます。これは、トランザクション途中の1ステートメントだけを再実行すると、処理順序や整合性が崩れる可能性があるためです。([Microsoft Learn][3])

たとえば、次の一連の処理を考えます。

在庫を減らす
注文データを登録する
決済履歴を登録する

2番目の処理で一時的なエラーが発生したからといって、2番目だけを再実行する設計は安全ではありません。

トランザクション中にデッドロックなどが発生した場合は、基本的に次の単位で処理します。

  1. 現在のトランザクションをロールバックする
  2. 必要に応じて接続を作り直す
  3. 新しいトランザクションを開始する
  4. トランザクション全体を最初から再実行する

トランザクション全体の再試行は、SqlClientのコマンドProviderへ任せるのではなく、アプリケーションのサービス層やデータアクセス層で設計します。

一時的エラーと恒久的エラーを分ける

Configurable Retry Logicは、エラーであれば何でも再試行する機能ではありません。

再試行で回復する可能性があるのは、主に次のような一時的な障害です。

再試行候補例
一時的な接続障害接続確立中の通信切断
データベースの一時的な利用不可フェールオーバー、切り替え処理
サービス側の負荷制限スロットリング、リソース不足
一時的な競合デッドロック、ロック待ちのタイムアウト

一方、次のような問題は再試行しても通常は解決しません。

再試行で直りにくい問題必要な対応
サーバー名が間違っている接続文字列を修正する
データベース名が間違っている接続先を確認する
認証情報が無効資格情報や権限を修正する
SQLの構文が間違っているSQLを修正する
テーブルや列が存在しないスキーマやSQLを修正する
暗号化設定が合わないクライアントとサーバー設定を見直す

組み込みProviderは、SqlClientが保持する一時的エラー一覧を基に再試行の可否を判断します。恒久的な設定ミスに対してNumberOfTriesを増やしても、障害検知が遅くなるだけです。([Microsoft Learn][5])

TransientErrorsを指定すると組み込み一覧が置き換わる

TransientErrorsを指定しない場合は、組み込みの一時的エラー一覧が使用されます。

var options = new SqlRetryLogicOption
{
    NumberOfTries = 5,
    DeltaTime = TimeSpan.FromSeconds(1),
    MaxTimeInterval = TimeSpan.FromSeconds(20),

    // nullのままなら組み込み一覧を利用
    TransientErrors = null
};

注意したいのは、TransientErrorsへ独自のエラー番号を設定すると、組み込み一覧へ追加されるのではなく、一覧そのものが置き換わる点です。([Microsoft Learn][3])

たとえば、次の設定ではエラー番号12345だけが対象になります。

TransientErrors = new[] { 12345 };

標準の一時的エラーを残したまま独自エラーを追加したい場合は、利用しているMicrosoft.Data.SqlClientのバージョンに対応した方法で、基準一覧と独自番号を結合する必要があります。

Microsoft.Data.SqlClient 7.0では、SqlConfigurableRetryFactory.BaselineTransientErrorsから組み込み一覧を取得できます。

var transientErrors =
    SqlConfigurableRetryFactory.BaselineTransientErrors
        .Append(12345)
        .ToArray();

BaselineTransientErrorsの利用可否はパッケージバージョンによって異なるため、導入中のバージョンで確認してください。([Microsoft Learn][5])

接続タイムアウトは必ず再試行されるとは限らない

再試行Providerを設定しても、あらゆるタイムアウトが再試行されるわけではありません。

Microsoftの説明では、クライアント側タイムアウトとして扱われるエラー-2は、組み込みの一時的エラー一覧に含まれていません。そのため、Connect TimeoutやCommandTimeoutが短すぎる場合は、再試行設定だけでは解決しないことがあります。([Microsoft Learn][2])

次の順番で設定を考えることが重要です。

  1. 1回の接続やコマンドが完了できるタイムアウトを確保する
  2. 一時的障害に対して再試行回数を設定する
  3. 再試行間隔を設定する
  4. アプリ全体の応答時間上限を設定する

MaxTimeIntervalだけを見て「最大20秒で失敗する」と判断しないようにしてください。

Pollyやcircuit breakerとの使い分け

Microsoft.Data.SqlClientのConfigurable Retry Logicは、SQL Server固有の一時的エラーを判断し、接続やコマンドの近くで再試行できる点が強みです。

一方、次のような要件は別の仕組みで扱います。

  • 一定回数失敗した後に処理を遮断するcircuit breaker
  • 代替処理へ切り替えるfallback
  • SQL Server以外のHTTP APIやストレージも含む再試行
  • 複数の依存サービスをまたぐ処理
  • アプリ全体の障害抑制
  • トランザクション全体の再実行

このような場合は、Pollyなどの汎用的なレジリエンス機構を検討します。Microsoft公式も、SQL単体の再試行にはSqlClient、circuit breakerや複数依存先をまたぐ制御には汎用ライブラリが適していると説明しています。([Microsoft for Developers][1])

ただし、SqlClientと外側の再試行処理を重ねる場合は試行回数に注意してください。

たとえば、次の設定を組み合わせたとします。

SqlClientのNumberOfTries = 5
外側の処理の総試行回数 = 3

外側の各試行でSqlClientが最大5回接続すると、単純計算では最大15回の接続試行が発生します。

再試行を多層化する場合は、どの層が何を再試行するのかを明確にします。

層担当させる処理の例
SqlClientSQL接続時の短時間の一時的エラー
データアクセス層トランザクション全体の再実行
アプリケーション層業務処理単位の再実行
汎用レジリエンス層circuit breaker、複数依存先の制御

失敗しやすい設定と対処

よくある失敗実際の動作対処
NumberOfTries = 5を5回再試行だと思う初回+最大4回総試行回数として計算する
接続にProviderを設定すればコマンドも再試行されると思う接続とコマンドは独立必要な場合だけコマンドにも設定する
すべてのSQLを再試行する更新処理が重複する可能性がある安全な処理だけ許可する
トランザクション中の1文だけ再試行する組み込みProviderでは再試行されないトランザクション全体を再実行する
TransientErrorsに1件追加する組み込み一覧が置き換わる基準一覧と結合する
MaxTimeIntervalを全体タイムアウトだと思う1回の待機時間の上限総所要時間を別途見積もる
恒久的な設定ミスを再試行で隠す失敗までの時間が延びる例外番号と原因を確認する
外側の再試行処理と重ねる試行回数が掛け算で増える各層の責任と上限を決める

Retryingイベントで再試行状況を記録する

一時的な障害が最終的に回復すると、通常の成功ログだけでは再試行が発生していたことを把握できません。

ProviderのRetryingイベントを利用すると、再試行回数、待機時間、直前の例外を記録できます。

retryProvider.Retrying += (_, eventArgs) =>
{
    var lastException =
        eventArgs.Exceptions[eventArgs.Exceptions.Count - 1];

    logger.LogWarning(
        lastException,
        "SQL接続を再試行します。RetryCount={RetryCount}, Delay={Delay}",
        eventArgs.RetryCount,
        eventArgs.Delay);
};

監視対象として、少なくとも次を記録します。

  • 再試行が発生した日時
  • 再試行回数
  • 待機時間
  • SQLエラー番号
  • 接続先を識別できる環境名
  • 最終的に成功したか、失敗したか

ただし、イベント内で時間のかかる処理を実行すると、再試行自体を遅らせます。ログ送信は軽量にし、接続文字列、パスワード、アクセストークンなどの機密情報を記録しないでください。MicrosoftのAPIリファレンスでも、再試行イベント内で重い処理を行わないよう注意されています。([Microsoft Learn][6])

導入時に確認するテスト項目

本番環境へ適用する前に、利用中のパッケージバージョンと実行環境で次を確認します。

  • Providerを設定していない場合は再試行されないこと
  • NumberOfTriesどおりの総試行回数になること
  • Retryingイベントが記録されること
  • 一時的エラーだけが対象になること
  • 認証エラーやSQL構文エラーが無意味に繰り返されないこと
  • コマンド用Providerが意図したSQLだけに適用されること
  • トランザクション内のコマンドが自動再試行されないこと
  • 障害時の総待機時間がアプリの許容時間に収まること
  • 外側の再試行処理と組み合わせても試行回数が過大にならないこと

再試行回数を増やせば安定するとは限りません。障害時に接続要求が集中すると、データベースの回復を遅らせる可能性もあります。まずは公式サンプルに近い小さな設定から始め、ログで実際のエラー番号と発生頻度を確認して調整する方法が現実的です。

まとめ:まずOpenAsyncの再試行から導入する

Microsoft.Data.SqlClientで接続エラーを自動再試行する基本手順は次のとおりです。

  1. System.Data.SqlClientの利用箇所を調べる
  2. Microsoft.Data.SqlClientへ移行する
  3. SqlRetryLogicOptionで試行回数と待機時間を定義する
  4. CreateExponentialRetryProvider()でProviderを作成する
  5. SqlConnection.RetryLogicProviderへ割り当てる
  6. OpenAsync()を実行する
  7. Retryingイベントで動作を監視する

NumberOfTries = 5は初回を含む5回の試行で、再試行は最大4回です。また、接続用Providerとコマンド用Providerは独立しています。

最初からすべてのSQLへ再試行を適用するのではなく、まず接続開始時の一時的エラーを対象にしてください。その後、ログで実際の障害を確認し、安全に繰り返せるコマンドだけを段階的に対象へ加える設計が適しています。
[1]: https://devblogs.microsoft.com/azure-sql/sqlclient-retry/ “Try the new SqlClient and Retry connections natively – Azure SQL Dev Corner”
[2]: https://learn.microsoft.com/en-us/sql/connect/ado-net/configurable-retry-logic?view=sql-server-ver17 “Configurable Retry Logic in SqlClient – ADO.NET Provider for SQL Server | Microsoft Learn”
[3]: https://learn.microsoft.com/en-us/sql/connect/ado-net/configurable-retry-logic-sqlclient-introduction?view=sql-server-ver17 “Configure Retry Logic in SqlClient – ADO.NET Provider for SQL Server | Microsoft Learn”
[4]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.data.sqlclient.sqlretrylogicoption?view=sqlclient-dotnet-core-6.1&utm_source=chatgpt.com “SqlRetryLogicOption Class (Microsoft.Data.SqlClient)”
[5]: https://learn.microsoft.com/en-us/sql/connect/ado-net/internal-retry-logic-providers-sqlclient?view=sql-server-ver17 “Built-In Retry Logic Providers in SqlClient – ADO.NET Provider for SQL Server | Microsoft Learn”
[6]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.data.sqlclient.sqlcommand.retrylogicprovider?view=sqlclient-dotnet-core-6.1&utm_source=chatgpt.com “SqlCommand.RetryLogicProvider Property (Microsoft.Data.SqlClient)”

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次