トップ回答者
ストアドプロシジャ実行中のネットワーク切断した場合のトラブルについて

質問
-
みなさま、はじめまして業務アプリケーション作っている新参者ですがSQLServerで困った症状がでており、解決方法がわからず質問をさせてください。
<<症状>>
遠隔地のPCでC#アプリケーションからSQLServer側のストアドプロシジャをコールして更新処理をさせています。
すべての処理はストアドプロシジャ側で行われC#アプリケーションは終了待ちを監視しているだけです。
今回そのストアドプロシジャ実行中に
「SQL Server への接続を確立しているときにネットワーク関連またはインスタンス固有のエラーが発生しました。サーバーが見つからないかアクセスできません。インスタンス名が正しいこと、および SQL Server がリモート接続を許可するように構成されていることを確認してください。 (provider: Named Pipes Provider, error: 40 - SQL Server への接続を開けませんでした)」
というエラーが発生しました。
今回、上記エラーが発生したため、データベースの内容を確認したところ対象行数1100
に対して、300行ほど処理した段階で停止していました。
<<質問事項>>
ここで、疑問なのはストアドプロシジャはネットワーク接続が切れると実行途中でも中断されてしまうのか?
SQLServerの設定もしくはストアドプロシジャの設定がおかしいのでしょうか?
■環境
<<SQLServerについて>>
SQLServerはデータセンターに導入してあります。SQLServerのバージョンは9.0.5000です
<<接続経路>>
遠隔地よりVPN接続を使いSQLServerへ接続しています
遠隔工場→VPN→弊社設備→弊社ルーター→専用線→データセンター
VPNはFortiVPNを利用していますが、瞬断が起きることが多々あります。
<<C#について>>
Microsoft Visual Studio Professional 2013
Version 12.0.31101.00 Update 4
Microsoft .NET Framework
Version 4.5.50938
インストールされているバージョン:Professional
Visual C# 2013
Microsoft Visual C# 2013
<<コールスタック>>
場所 System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
場所 System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
場所 System.Data.SqlClient.TdsParser.Connect(ServerInfo serverInfo, SqlInternalConnectionTds connHandler, Boolean ignoreSniOpenTimeout, Int64 timerExpire, Boolean encrypt, Boolean trustServerCert, Boolean integratedSecurity, Boolean withFailover)
場所 System.Data.SqlClient.SqlInternalConnectionTds.AttemptOneLogin(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean ignoreSniOpenTimeout, TimeoutTimer timeout, Boolean withFailover)
場所 System.Data.SqlClient.SqlInternalConnectionTds.LoginNoFailover(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString connectionOptions, SqlCredential credential, TimeoutTimer timeout)
場所 System.Data.SqlClient.SqlInternalConnectionTds.OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectionOptions, SqlCredential credential, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance)
場所 System.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, SqlCredential credential, Object providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString userConnectionOptions, SessionData reconnectSessionData)
場所 System.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions)
場所 System.Data.ProviderBase.DbConnectionFactory.CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions)
場所 System.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
場所 System.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
場所 System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)
場所 System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection)
場所 System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)
場所 System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
場所 System.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
場所 System.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry)
場所 System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)
場所 System.Data.SqlClient.SqlConnection.Open()
場所 <<弊社アプリケーション>>.TableAdapters.QueriesTableAdapter.UPDATE()
- 編集済み T.Furui 2015年3月6日 4:13
回答
-
サーバー側の挙動についての話なので、SQL Server ログなどサーバー側のログもチェックしてくださいね。
で、このストアドプロシージャは以下に該当しますか?
・複数の DML / DDL が記述されている
・複数のトランザクションで構成されている、もしくは明示的なトランザクションを記述していない
もし上記に該当して、かつネットワークの瞬断が起きた場合は記載のような状況になります。
>ここで、疑問なのはストアドプロシジャはネットワーク接続が切れると実行途中でも中断されてしまうのか?
その通りです。
>SQLServerの設定もしくはストアドプロシジャの設定がおかしいのでしょうか?
おかしいのではなく、システム構成と設定と仕様の組み合わせでそのような挙動になっているだけです。処理の途中でネットワークが瞬断した場合は以下の挙動になります。
- クライアントからサーバーへ処理のリクエストされる。
この場合はクライアントから実行すべきストアドプロシージャの名前などがサーバーへ送信されます。 - サーバーはリクエストを実行する。
- サーバーからクライアントへレスポンスを返す必要がある場合に、サーバーからクライアントへ通信を行う。
必要が無い間は、TCP/IP の Keepalive などで接続を維持する。
※ストアドプロシージャの途中で結果セットや実行行数を返す処理があると処理途中(今回は 300 行目付近)で送信します。
※特にそのような処理が無い場合は、全ての実行が終わった段階で結果を送信します。 - ここで瞬断によって通信が切れている場合、サーバーとクライアントそれぞれ別の動きをします。
サーバーはクライアントへ送信できなかったため、セッションを切断し、処理を中断します。
未コミットのトランザクションはロールバックされます。
クライアント側は実行結果を受信できないためクエリタイムアウトに至り、キャンセル要求をサーバーへ送信しますが、このキャンセル要求の通信が出来ないため、セッションが切断されていることを認識し、この場合は例外が Throw されます。
瞬段が想定される環境でシステムを構成する場合は、トランザクション制御や、ストアドプロシージャの実行をジョブやバッチの形でサーバー側で完結させてクライアント側からはその予約と結果取得を行う形にしたり、等々、適切な設計を考慮、実装すべきかと思います。
MCITP(Database Developer/Database Administrator)
- クライアントからサーバーへ処理のリクエストされる。
すべての返信
-
サーバー側の挙動についての話なので、SQL Server ログなどサーバー側のログもチェックしてくださいね。
で、このストアドプロシージャは以下に該当しますか?
・複数の DML / DDL が記述されている
・複数のトランザクションで構成されている、もしくは明示的なトランザクションを記述していない
もし上記に該当して、かつネットワークの瞬断が起きた場合は記載のような状況になります。
>ここで、疑問なのはストアドプロシジャはネットワーク接続が切れると実行途中でも中断されてしまうのか?
その通りです。
>SQLServerの設定もしくはストアドプロシジャの設定がおかしいのでしょうか?
おかしいのではなく、システム構成と設定と仕様の組み合わせでそのような挙動になっているだけです。処理の途中でネットワークが瞬断した場合は以下の挙動になります。
- クライアントからサーバーへ処理のリクエストされる。
この場合はクライアントから実行すべきストアドプロシージャの名前などがサーバーへ送信されます。 - サーバーはリクエストを実行する。
- サーバーからクライアントへレスポンスを返す必要がある場合に、サーバーからクライアントへ通信を行う。
必要が無い間は、TCP/IP の Keepalive などで接続を維持する。
※ストアドプロシージャの途中で結果セットや実行行数を返す処理があると処理途中(今回は 300 行目付近)で送信します。
※特にそのような処理が無い場合は、全ての実行が終わった段階で結果を送信します。 - ここで瞬断によって通信が切れている場合、サーバーとクライアントそれぞれ別の動きをします。
サーバーはクライアントへ送信できなかったため、セッションを切断し、処理を中断します。
未コミットのトランザクションはロールバックされます。
クライアント側は実行結果を受信できないためクエリタイムアウトに至り、キャンセル要求をサーバーへ送信しますが、このキャンセル要求の通信が出来ないため、セッションが切断されていることを認識し、この場合は例外が Throw されます。
瞬段が想定される環境でシステムを構成する場合は、トランザクション制御や、ストアドプロシージャの実行をジョブやバッチの形でサーバー側で完結させてクライアント側からはその予約と結果取得を行う形にしたり、等々、適切な設計を考慮、実装すべきかと思います。
MCITP(Database Developer/Database Administrator)
- クライアントからサーバーへ処理のリクエストされる。
-
nagino様 丁寧なご回答ありがとうございます。
確かに今回のストアドプロシジャは複数の処理をさせておりました。
呼び出しは結果セットは不要なのでExecuteNonQueryで実行しています。
>>サーバー側で完結させてクライアント側からはその予約と結果取得を行う形
このように作ったつもりになっていました。いままで、MS-ACCESSでのスタンドアローン案件ばかりでしたのでよくわかっておらず、お恥ずかしい話です。
--------------------------------------------------------------------------------
ストアドプロシジャの概要が書かれていないことに気づきました。
次のようなことをさせています。1)一時表の準備(2種類の一時表の作成)
1-1)更新される表から更新対象行を 更新先一時表 作成
1-2)更新元の ループ用一時表 作成2)更新前準備
2-1)ループ一時表のデータ1行取り出し
2-2)更新先一時表を検索して、更新処理
2-3)更新元データの更新結果欄を更新
2-4)ループ一時表のデータがなくなるまで2-1から繰り返し3)更新
3-1)更新先一時表を実際の更新先へ適用4)更新元データの保管
5)一時表の破棄
6)終了
今回の瞬断はデータベースの内容から2)の前準備工程で300行ほど処理をしたところで起きたものと想像します。
今回の瞬断に対応できるのかどうかわからないのですが、ストアドプロシジャ先頭でSET NOCOUNT ONと書いていなかったので、それを追加しました。