トランザクション地獄

過去の記事で、プラットフォームでトランザクション管理について既に言及しました 。 この記事では、トランザクションの実装、管理などについて詳しく説明します。



最初から、アプリケーションサーバーが「トランザクション整合性」をサポートする必要があると判断しました。 この用語は、アプリケーションサーバーへの呼び出しが成功するか、すべての変更を元に戻す必要があることを意味します。 したがって、サーバー呼び出しの処理の開始時にトランザクションが作成され(正確には、データベースの最初の変更時に発生します)、呼び出しが終了すると修正(またはキャンセル)されます。





同時に、アプリケーション開発者はデータベースへの接続に直接アクセスできず、すべての通信は対応するレイヤーを介して行われます。 その結果、当然、アプリケーション開発者は修正要求の実行(たとえば、truncateが実行されるプロシージャの実行)を試みることがありますが、そのような開発者は脚のショットから免れることはありません。

はい、そのような実装は、理論的には時間を増加させ、結果として、過度のブロッキングの可能性を増加させます。 実際には、これを回避するのは簡単です。 トランザクションの整合性により、大量(約5MB)のソースコード(アプリケーション部分)が頻繁に変更される条件下で、データを一貫した状態に維持できます。 次に、通常、次のシナリオの結果として、その不在は一貫性の違反につながります。

UpdateDocument関数にいくつかの機能が実装されているとします:

public void UpdateDocument(Document doc) { try { // -       Database.Commit(); } catch (Exception ex) { Log("   : ", ex); Database.Rollback(); } }
      
      





UpdateDocumentメソッドを呼び出すコマンドがドキュメントにあるとします。

 public void Command(Document doc) { try { UpdateDocument(doc); //  -  if (somethingWrong) { throw new Exception(" !"); } Database.Commit(); } catch (Exception e) { Log("    : ", ex); Database.Rollback(); } }
      
      





これら2つの関数が並んで記述され、変更されない場合、問題に気付くのは簡単です。Command関数でUpdateDocumentを呼び出した後、この呼び出しの前に行われた変更の一部がコミットされます。 将来問題が発生した場合、UpdateDocumentを呼び出した後に行われた変更のみがロールバックされます。

UpdateDocumentを呼び出した後、新しいトランザクションが開始されることはそれほど明白ではありません。 これには多くの結果があります。たとえば、一時テーブルが空になったり、変更をコミットする前に設定されたロックが削除されたりするため、新しいトランザクションでは、クエリは予想外の値を返します。

この種のエラーは診断が非常に困難であり、さらに、問題が非常に深刻な日、月、または年でも誰も気付かないようなデータの段階的な腐食につながる可能性があります。

そのため、トランザクションの整合性を備えた実装オプションを選択しました。 ただし、中間固定を行う必要がある場合があります。 たとえば、ソリューションのタスクの1つは、「埋蔵量の除去」を扱います。 実際、ドキュメントを調べて順番に変更します。 伝統的に、最初のバージョンでどのように実装されたかを説明します(理解するのに余分なコードはすべて削除されました)。

 .... foreach(docId in docs) { try { var doc = DocumentManager.GetDocument(docId); doc.state = DocStateCancelled; DocumentManager.Save(doc); Server.Commit(); } catch(Exception e) { LogManager.Log("something is wrong"); } } ...
      
      





ロジックが変更され、Saveメソッドが例外をスローし始めるまで、すべてが正常に機能しました。 これにより、データに奇妙なアーティファクトが出現し、その原因を特定することは非常に困難でした。

そして、これが何が起こったのかです-ある反復で例外がスローされ、ログに書き込まれました。 そして、次の成功した反復で、例外をスローする前に行われた変更が記録されました!

解決策として、現在の接続へのアクセスの可能性を放棄することを提案しましたが、新しい接続を要求する機会を提供するために、すでに変更をコミットすることが可能です。 同時に、明示的に新しい接続を要求しないネストされた呼び出しはすべて、呼び出しスタックの上で受信した接続で機能します。

コードは次のようになります。

 .... foreach(docId in docs) { using (var ts = new TransactionScope()) { try { var doc = DocumentManager.GetDocument(docId); doc.state = DocStateCancelled; DocumentManager.Save(doc); ts.Complete(); } catch(Exception e) { LogManager.Log("something is wrong"); } } } ...
      
      





内部のすべての呼び出し(ネストを含む)は、CreateTransactionScope呼び出しで作成されたトランザクションを使用します。 ご覧のとおり、開発者は自分で接続を取得することを心配する必要はありません。さらに、このためのツールはありません。 ネストされた呼び出しのアプリケーション開発者は、必要に応じて、中間データを修正して、新しいトランザクションのみを要求できます。 したがって、実際には言語構造のレベルで、データの腐食につながる中間的な固定は不要です。



結論として



同様の方法で対処できるデータの腐食につながる他のオプションがあります。 将来の記事でそれらについて話をしようとします。



All Articles