私は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%まで)。
デッドロックに関連する例外が多数あります。何故ですか?そのような単純なコードがどのようにデッドロックを引き起こす可能性があります。つまり、それはちょうど選択とそれに続く挿入です。
提案がありますか?
H2とストアエンジンのバージョンは何ですか? – fg78nc
私はそれを確認する方法がわかりません。私は特定のテーブルのエンジンを照会するためにいくつかのMySQLコードを見つけましたが、それはH2で動作していないようです。バージョンに関しては、バージョン番号なしで自分の 'pom.xml'の依存関係をリストアップしています。それで、最新の(?)かもしれません。 –
データベースURLに 'LOCK_MODE = 1; MVCC = TRUE; 'を追加してみてください。 – fg78nc