2016-11-21 12 views
3

最近、私たちはSQLトランザクションのタイムアウトと関連して非常に興味深い問題に直面しました。タイムアウトした文は本当に問題のために重要ではありませんが、それは、単一のINSERT文だったのwクライアントとの明示的なトランザクションをキーとしてGUIDを生成したO /:SQLトランザクションタイムアウトに対処するための防御的アプローチ

INSERT MyTable 
(id, ...) 
VALUES (<client-app-generated-guid>, ...) 

は、我々はまた、インプレース再試行ポリシーを持っていますしたがって、コマンドがSqlExceptionで失敗した場合は、再試行されます。 SQL Server(Azure SQL)はいつも正常に動作せず、再試行中に多くの奇妙なPK違反エラーに遭遇しました。それらはを実際に正常にをコミットしてをコミットして、SQL Serverのトランザクションで(これにより、既にIDを取得しているため)挿入されました。私はSQLのタイムアウトはpurely client side conceptだから、もしクライアントがSqlCommandが失敗したと考えるなら、それはかもしれないし、そうでないかもしれないと理解している。を意味する。

クライアントの明示的なトランザクション制御は、TransactionScopeのようなラッピングステートメントを使用すると、このような問題の99%が修正されます。実際にCommitはかなり高速です。&安価な操作です。しかし、私はまだそこに警告を見る - タイムアウトはコミットステージでも起こりうる。このアプリケーションは、トランザクションが実際にコミットされたかどうかを推測することが不可能な状況(リトライの必要性を把握するため)にも、再び存在する可能性があります。

防弾でコードを書いて(そのような種類の問題に)、の一般ファッションをどうやって書いて、トランザクションがコミットされていないことが明確になったらリトライしてください。

using (var trx = new TransactionScope()) 
using (var con = GetOpenConnection(connectionString)) 
{ 
    con.Execute("<some-non-idempotent-query>"); 

    // what if Complete() times out?! 
    // to retry or not to retry?! 
    trx.Complete(); 
} 
+0

COMMIT TRANが実際に正常に実行されたという確認をクライアントがSQL Serverから受け取る前に、フレームワークコードがコミット済みトランザクションを99%報告する方法を持っていても、迅速にプラグをプルすることができます。 SQL AzureのSLA/Latencyのためだけに、これはSQL Azureでは問題が増え、On-premesisでは問題にはならないと思います。 あなたのコミットが成功したかどうかを伝える方法はありません。読み取り後の書き込みであっても、大量の環境で誰かの変更を単に取り上げる可能性があります。 – PhillipH

+0

@PhillipH、ありがとう!そのような問題をいかに克服するか? –

+0

あなたのコミットが失敗した場合、再試行処理ロジックで、 'INSERT ... WHERE NOT EXISTS(SELECT id FROM MyTable WHERE id = @id)'のように条件付きの再挿入が必要な場合があります。 – Alex

答えて

2

問題は、例外がトランザクションが失敗したことを意味しないということです。補償作業(再試行のような)については、失敗したかどうかを明確に伝える必要があります。私が提案するものにはスケーラビリティの問題がありますが、その重要な技術であるスケーラビリティの問題は他の方法で解決できます。

私の解決策。

  1. COMMITの前の最後のINSERTは、追跡テーブルにGUIDを書き込むことです。
  2. 例外が発生した場合、ネットワークに障害が発生したことを示します。SELECT @@ TRANCOUNT。あなたがまだ取引中であることを示している場合(おそらく決して起こらないはずですが、それは価値があります)、COMMITをうれしく再送信することができます
  3. @@ TRANCOUNTが0を返した場合、トランザクション。トラッキングテーブルからGUIDを選択すると、COMMITが成功したかどうかがわかります。
  4. あなたのコミットが成功しなかった場合(@@ TRANCOUNT == 0で、あなたのGuidがトラッキングテーブルに存在しない場合)、BEGIN TRANSACTION以降のバッチ全体を再送信してください。
+0

ありがとう!もっと一般的なものはどうですか?私たちは、任意の非冪等のコードのために働くアプローチを思いつくことができますか? –

+1

私の例の一般的なパターンは、すべてのSQLバッチで技術的に機能します。しかしながら;すべてのSQLバッチは、通常、トランザクションバッチ内で見つかったデータベースの状態(バッチの準備で見つかった以前の状態)に依存します。一度接続が失われると、バッチロジックが同じように繰り返されることは保証できません。ビジネスプロセスごとに何らかの報酬制度を構築する必要があるかもしれません。バッチを繰り返すだけでは不適切かもしれません。最初にトランザクションを構築するコードと同じくらい重要なものであると考えてください。 – PhillipH

+0

トランザクションが「更新アカウントセット残高=残高+ 100ここでid = 123」のようなものであれば、そのような提案をどのように翻訳するのですか? –

2

一般的なアプローチは次のとおりです。挿入しようとしたものを読み返してみてください。

挿入しようとしたIDを読み取ることができ、以前のトランザクションが正常にコミットされた場合は、再試行する必要はありません。

挿入しようとしたIDが見つからない場合は、挿入しようとして失敗したため、再試行する必要があります。


いずれのSQL文でも機能する完全な汎用パターンを持つ方法はありません。あなたの "チェック"コードは何を探すべきかを知る必要があります。

IDがINSERTの場合は、そのIDを探しています。

UPDATEの場合、チェックはカスタムであり、その内容はUPDATEに依存します。

DELETEの場合は、削除対象の内容を読み取ろうとしています。


実は、ここに一般的なパターンです:データ修正のいくつかのID(DELETE文は、いくつかのGUIDを挿入し、そのトランザクション内の1つの以上INSERT文を持っている必要があり、一つまたは複数のINSERTを持つ任意のデータ修正バッチ、UPDATEトランザクション自体)を専用の監査テーブルに格納します。チェックコードは、その専用の監査テーブルから同じGUIDを読み取ろうとします。 GUIDが見つかった場合、以前のトランザクションが正常にコミットされたことがわかります。 GUIDが見つからない場合、前のトランザクションがロールバックされたことがわかり、再試行できます。

この専用の監査テーブルを持つことで、チェックを統一/​​標準化します。チェックは内部コードやデータ変更コードの詳細に依存しなくなりました。データ変更コードと検証コードは、同じ合意されたインターフェース - 監査テーブルに依存します。

+0

この3番目のセクションはまさに私の心の中にありました!それを公開していただきありがとうございます。とにかく他の反応を待ってみましょう。 –

+0

私は解決策としてPhillipHの答えをマークしました。これは簡単にあなたの提案と同じですが、彼は最初に答えました。再びありがとう、ウラジミール! –

関連する問題