Ich arbeite an einer REST-API in Java mit Spring Boot implementiert. Ich habe die eingebettete In-Memory-Datenbank H2 für mehrere Wochen benutzt, aber irgendwann ist mir aufgefallen, dass etwas mit der Transaktionsisolation nicht stimmt.Spring JPA + MySQL und Deadlocks
Genauer gesagt habe ich eine Tabelle, wo ich "doppelte" Aufzeichnungen verfolgen muss. Ein Duplikat ist nur ein Datensatz, der einem anderen für eine genau definierte Teilmenge der Spalten der Tabelle entspricht. Wenn ich also einen neuen Datensatz einfüge, prüfe ich zuerst, ob es ein Duplikat ist und markiere es entsprechend. Zu diesem Zweck dient eine boolesche Spalte "duplizieren".
Nehmen wir zum Beispiel an, dass B und C die Spalten sind, die ich überprüfe, um Duplikate zu definieren. Dies ist ein gültiger Zustand:
| A | B | C | duplicate | | - | - | - | --------- | | x | y | z | false | | z | y | z | true | | x | y | y | false | | x | y | y | true | | y | y | y | true |
während dieser nicht ein gültiger Zustand ist:
| A | B | C | duplicate | | - | - | - | --------- | | x | y | z | false | | z | y | z | true | | x | y | y | false | | x | y | y | true | | y | y | y | false |
... weil Zeile 3 und Zeile 5 die gleichen Werte für beide B und C, also muss einer der beiden als Duplikat markiert werden.
Mit anderen Worten, meine Anforderung ist, jede Zeile zu duplizieren, die zufällig bereits Werte verwendet hat. Nur eine Zeile für eine gegebene Menge von Werten darf duplicate == false
haben.
Meine Spring-basierte Implementierung funktionierte jedoch nicht wie erwartet. Wenn Sie beispielsweise 100 Zeilen mit denselben Werten einfügen, sollten 99 Duplikate und nur ein Duplikat entstehen. Aber als ich versuchte, diese Inserts parallel auszuführen, wurden viele Duplikate nicht erkannt.
Ich habe mehrere Fixes ausprobiert und irgendwann habe ich angefangen zu denken, dass H2 die SERIALIZABLE Isolationsstufe nicht korrekt implementiert hat. Ich habe eine kleine Anwendung, es zu zeigen:
@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);
}
}
Hinweis: ich offensichtlich Code weglassen wie Modelle und Repositories. Das Foo
Modell hat eine Kennung (Typ UUID) eine bar
Eigenschaft (Typ String) und eine duplicate
Eigenschaft (Typ Boolean). Die doppelte Überprüfung basiert auf der Eigenschaft bar
.
Mit H2 habe ich viele verpasste Duplikate (10% normalerweise). Mit MySQL habe ich immer korrekte Ergebnisse (d. H. Die Anzahl der als doppelt markierten Zeilen ist genau N - 1 wobei N die Anzahl der eingefügten Zeilen ist). Das einzige Problem ist, dass nur ein kleiner Teil der Inserts erfolgreich ist (von 1% bis maximal 30%).
Ich habe eine große Anzahl von Deadlock-bezogenen Ausnahmen. Warum das? Wie konnte solch ein einfacher Code einen Deadlock verursachen? Ich meine, es ist nur ein Select gefolgt von einem Insert.
Irgendwelche Vorschläge?
Was ist die Version von H2 und Store-Engine verwenden Sie? – fg78nc
Ich weiß nicht, wie ich es überprüfe. Ich habe MySQL-Code gefunden, um die Engine einer gegebenen Tabelle abzufragen, aber es scheint nicht auf H2 zu funktionieren. Wie für die Version, listet ich die Abhängigkeit in meiner 'pom.xml' ohne eine Versionsnummer auf, also ist es vielleicht die neueste (?). –
Bitte versuchen Sie, zur Datenbank URL 'LOCK_MODE = 1; MVCC = TRUE;' – fg78nc