2017-06-16 20 views
2

私はSpringブートでJavaで実装されたREST APIを開発中です。私は埋め込まれたメモリ内のデータベースH2を数週間使用しましたが、ある時点ではトランザクション分離に間違いがあることに気付きました。Spring JPA + MySQLとデッドロック

もっと正確には、私は "重複"レコードを追跡する必要があるテーブルを持っています。重複は、テーブルの列の明確に定義されたサブセットの別のレコードと同じです。ですから、基本的には、新しいレコードを挿入するときには、重複しているかどうか最初にチェックし、それに応じてマークします。この目的のために、ブール値の列「複製」が使用されます。

たとえば、BとCが重複を定義するためにチェックする列であるとします。これは有効な状態である。

| A | B | C | duplicate | | - | - | - | --------- | | x | y | z | false | | z | y | z | true | | x | y | y | false | | x | y | y | true | | y | y | y | true |

このない有効な状態である間:3行目と5行目には、両方のBに対して同じ値を持っているので...

| A | B | C | duplicate | | - | - | - | --------- | | x | y | z | false | | z | y | z | true | | x | y | y | false | | x | y | y | true | | y | y | y | false |

とC、2つのうちの1つが重複としてマークされる必要があります。

つまり、私の要件は、すでに値を使用していた行を重複としてマークすることです。与えられた値のセットに対して1行だけがduplicate == falseを持つことが許されます。

ただし、私のSpringベースの実装は期待どおりに動作していませんでした。たとえば、同じ値を持つ100個の行を挿入すると、重複が99個、非重複が1個だけになります。しかし、これらの挿入を並行して実行しようとすると、重複が多数検出されませんでした。

私はいくつかの修正を試みましたが、ある時点で、H2がSERIALIZABLE分離レベルを正しく実装していなかったと思い始めました。

@RestController 
public class NewFooCtrl { 

    @Autowired 
    private FooRepo repo; 

    @RequestMapping(value = "/foo", method = RequestMethod.POST) 
    @Transactional(isolation = Isolation.SERIALIZABLE) 
    public void newFoo(@RequestBody Foo foo) { 
    List<Foo> foos = repo.findByBar(foo.getBar()); 
    if (foos.isEmpty()) foo.setDuplicate(false); 
    else foo.setDuplicate(true); 
    repo.save(foo); 
    } 

} 

注:私はそのようなモデルとリポジトリとして明白なコードを省略し、私はそれを実証するための小さなアプリケーションを作成しました。 Fooモデルは識別子(タイプUUID)a barプロパティ(タイプString)とduplicateプロパティ(タイプboolean)を持ちます。重複チェックはbarプロパティに基づいています。

H2の場合、重複しているものが多い(通常10%)。 MySQLでは常に正しい結果が得られます(つまり、重複としてマークされた行の数は、、正確には N - 1であり、Nは挿入された行の数です)。唯一の問題は、インサートのごく一部しか成功しないことです(せいぜい1%から30%まで)。

デッドロックに関連する例外が多数あります。何故ですか?そのような単純なコードがどのようにデッドロックを引き起こす可能性があります。つまり、それはちょうど選択とそれに続く挿入です。

提案がありますか?

+0

H2とストアエンジンのバージョンは何ですか? – fg78nc

+0

私はそれを確認する方法がわかりません。私は特定のテーブルのエンジンを照会するためにいくつかのMySQLコードを見つけましたが、それはH2で動作していないようです。バージョンに関しては、バージョン番号なしで自分の 'pom.xml'の依存関係をリストアップしています。それで、最新の(?)かもしれません。 –

+1

データベースURLに 'LOCK_MODE = 1; MVCC = TRUE; 'を追加してみてください。 – fg78nc

答えて

1

デッドロック関連の例外は、私がデモアプリケーションをテストしていた方法に起因すると考えられます。より正確には、テストコードはJavaScript/Node.jsで書かれていましたが、は非常にで、I/Oタスクの開始には高速です。ほとんどの場合、すべてのトランザクションが同時に要求されました(とは同時にを再試行します)。

各リクエストの間に非常に短い待ち時間(たとえば10ミリ秒)を追加することで、妥当なスループットと非常に少ないデッドロック関連の例外が得られました。

私の推測では、デッドロックはまったくありません。ちょうど非常に高いロック競合。データベースレベルでのある種のヒューリスティックは、デッドロックの可能性があると解釈します。実際、MySQL CLIからデッドロック検出機能を無効にすることで、デッドロック関連の例外は完全に排除されました(ただし、ロック待ちタイムアウトに置き換えられました)。

2

アプリケーションは、トランザクション内で重複するキー自体をチェックするべきではありません。これを一意のインデックスを持つデータベースエンジンに残し、発生した場合はExceptionをキャッチして、別の識別子を使用して再試行します。

これをアプリケーションレベルで本当に解決したい場合は、トランザクションを開くとすぐに手動でテーブルをロックする必要があります。隔離レベルは、自動的にこれを行うことができますが、パフォーマンスの高いコスト(必要のない可能性が高い)で実行できます。

別の解決策は、@Versionアノテーションを使用して楽観的なロックを行うことですが、識別子の一意性を保証することはできません。


あなたのデッドロックの問題を診断することは困難ですが、再帰的な取引(トランザクション別のトランザクションでオープン)を持っているとき、それは通常表示されます。あなたの豆@Scopeをチェックすると、そのような問題が生じる可能性があります。また、TransactionManagerEntityManager beanが1つだけあることを確認してください。

+1

こんにちはGuillaume、ありがとう。 DBレベルで重複をチェックするのは合理的な選択肢ですが、私はアプリケーションレベルでチェックを続けたいと思っています。デッドロックに関しては、私のデモ・アプリケーションは本当にシンプルです。もちろん、私は豆のネストされたトランザクションやデフォルトではない(シングルトン)スコープを持っていません。私の推測では、これらのデッドロック関連の例外はすべてデッドロック検出のヒューリスティックの結果にすぎないが、実際のデッドロックはないということです。 –