2017-06-16 11 views
2

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?

+0

Was ist die Version von H2 und Store-Engine verwenden Sie? – fg78nc

+0

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 (?). –

+1

Bitte versuchen Sie, zur Datenbank URL 'LOCK_MODE = 1; MVCC = TRUE;' – fg78nc

Antwort

1

Ich denke, die Deadlock-Ausnahmen wurden durch die Art, wie ich die Demo-App getestet wurde verursacht. Genauer gesagt wurde der Testcode in JavaScript/Node.js geschrieben, was extrem ist, wenn es darum geht, E/A-Aufgaben zu starten. Alle Transaktionen wurden fast gleichzeitig angefordert (und vielleicht automatisch gleichzeitig versucht?).

Durch Hinzufügen einer sehr kurzen Wartezeit (z. B. 10 ms) zwischen jeder Anforderung erhielt ich einen angemessenen Durchsatz und eine sehr geringe Anzahl von Deadlock-bezogenen Ausnahmen.

Meine Vermutung ist, dass es überhaupt kein Deadlock gibt. Nur sehr hohe Sperrkonflikte, die eine Art Heuristik auf Datenbankebene als mögliche Deadlocks interpretiert. Durch die Deaktivierung der Deadlock-Erkennung von der MySQL CLI habe ich diese Deadlock-bedingten Ausnahmen vollständig eliminiert (obwohl sie durch Sperrwartezeitüberschreitungen ersetzt wurden).

2

Die Anwendung sollte in einer Transaktion nicht selbst nach doppelten Schlüsseln suchen. Überlassen Sie dies dem Datenbankmodul mit einem eindeutigen Index, fangen Sie die Ausnahme ab, falls sie auftritt, und versuchen Sie es erneut mit einem anderen Bezeichner.

Wenn Sie dies wirklich auf Anwendungsebene lösen möchten, sollten Sie die Tabelle möglicherweise manuell sperren, sobald Sie die Transaktion öffnen. Die Isolationsstufe kann dies automatisch für Sie erledigen, jedoch zu hohen Leistungskosten (die Sie wahrscheinlich nicht möchten).

Eine andere Lösung wäre optimistisches Sperren mit der @Version Annotation, aber dann werden Sie nicht in der Lage sein, die Eindeutigkeit des Bezeichners zu garantieren.


Es ist schwierig, Ihr Deadlock Problem zu diagnostizieren, aber es scheint, in der Regel, wenn Sie rekursiv Transaktionen (eine Transaktion in einer anderen Transaktion geöffnet). Überprüfen Sie Ihre Bohnen @Scope, sie können solche Probleme erstellen. Stellen Sie außerdem sicher, dass Sie nur eine TransactionManager und eine EntityManager Bean haben.

+1

Hallo Guillaume, danke. Das Überprüfen von Duplikaten auf DB-Ebene ist eine vernünftige Option, aber ich bevorzuge es, die Überprüfung auf Anwendungsebene zu behalten. Was die Deadlocks betrifft, so ist meine Demo-App wirklich einfach und natürlich habe ich keine verschachtelten Transaktionen oder nicht standardmäßigen (Singleton) Bereich für meine Beans. Meine Vermutung ist, dass alle diese Deadlock-bezogenen Ausnahmen nur das Ergebnis einer Deadlock-Erkennungsheuristik sind, aber es gibt keinen tatsächlichen Deadlock. –