2016-08-18 1 views
1

Ich habe eine Tabelle, die ein paar Spalten und eine Sequenz, die für den Wert dieser Spalten erhöht hat. Für (ein vereinfachtes) Beispiel:Ruhezustand Gleichzeitigkeit Problem mit Auswahl max() und dann einfügen() + 1

A | B | SEQ 
=========== 
x | x | 1 
x | x | 2 
x | x | 3 
x | y | 1 
x | x | 4 
z | x | 1 
x | y | 2 

Ich habe mehrere Prozesse, die eine Feder-Anwendung mit der Ruhe verwenden werden und wird sowohl A als auch B, wählen Sie den Maximalwert für A und B annehmen, und dann max + 1 einzufügen.

So zum Beispiel oben, wenn jemand A vorgesehen = z und B = x, würden wir wählen, erhalten 1 und legen Sie z, x, 2.

Wenn jemand A = z und die B = z (keine vorhandenen Zeilen übereinstimmen), der Prozess würde abfragen und keine Max (oder ein Maximum von Null) finden und dann z, z, 1. einfügen.

Angenommen, dass eine einfache Sequenz hier nicht funktioniert, weil die Spaltengröße für Sequenz wird nicht groß genug sein, um eine Sequenz über alle Werte von A und B zu ermöglichen, und weil das tatsächliche Problem komplexer ist als das oben angegebene Beispiel.

Ich kann mir vier Lösungen für dieses Problem vorstellen, kann funktionieren aber ich frage mich, ob es eine Standardmethode ist, dies zu tun, und ich bin mir nicht sicher, wie diese Methoden mit Hibernate und ohne implementiert werden können Verwenden benutzerdefinierter Oracle-Funktionen:

  1. Führen Sie die select max(), versuchen Sie, einzufügen. Wenn ein Problem mit dem gemeinsamen Zugriff auftritt, wird einer der Threads fehlschlagen und der Code wird so oft wie erforderlich wiederholt, bis die Einfügung funktioniert. Mit Hibernate könnte dies bedeuten, dass wir mitten in unserer Transaktion manuell leeren müssten.
  2. Führen Sie einen select for update oder einen anderen Mechanismus durch, um die Zeilen zu sperren, die uns wichtig sind (diejenigen, die A und B entsprechen), wenn wir die erste select max() machen. Dies wird andere Prozesse blockieren, die ebenfalls versuchen, das Maximum zu finden, und dann können wir unsere Zeile einfügen, und wenn das Txn festgeschrieben wird, können die ausgewählten Elemente ausgewählt werden. Ich bin mir nicht sicher, wie das funktionieren würde, wenn es derzeit keine Zeilen in der Tabelle gibt, die mit A und B übereinstimmen.
  3. Erstellen Sie eine gespeicherte Prozedur, die das Maximum automatisch auswählen und einfügen kann. Ich möchte jedoch gespeicherte Prozesse möglichst vermeiden.
  4. In Hibernate gibt es eine Möglichkeit, eine Einfügung (select max() + 1) durchzuführen und den Wert aus einer Spalte, die in einer Anweisung eingefügt wurde, zurückzugeben?

Antwort

0

Nicht sicher, ob dies für Sie funktioniert. Aber so würde ich es machen, denn der Versuch, die Parallelität aufrechtzuerhalten, kann kompliziert sein.

Zuerst speichere ich die Zeit des Einfügens.

INSERT INTO yourTable (`A`, `B`, `insertTime`) VALUES(x, x, NOW()); 

dann Ihre Abfrage

SELECT `A`, `B`, 
     ROW_NUMER() OVER (PARTITION BY `A`, `B` ORDER BY `insertTime`) as SEQ 
FROM yourTable 
//optional to get the exact result you show 
ORDER BY `insertTime` 

EDIT werden: Dies kann nicht funktionieren, wenn Sie löschen möchten, und halten nach dem letzten seq wächst.

+0

Danke für den Vorschlag. Wir werden wahrscheinlich keine Löschungen in dieser Tabelle haben. 'partition by' würde natives SQL erfordern und das möchte ich vermeiden. –

+0

Sie sind so wählerisch, zuerst nicht storeproc, jetzt nicht native sql verwenden. Oracle hat 'row_number() über (Partition' –

+0

hast du dieses überprüft? Http://stackoverflow.com/questions/3616321/concurrent-updates-handling-in-hibernate?rq=1 –

0

Was ist mit Aggregations Tabelle:

A | B | LAST_SEQ 
=========== 
x | x | 4 
z | x | 1 
x | y | 2 

Dann können Sie leicht auf Zeilenebene Sperre verwenden, um sicher Datensätze in der 1. Tabelle zu erzeugen.

+0

Wir könnten dies tun, aber wir don kenne nicht alle Kombinationen von A und B (und es wird mehr Spalten im realen Szenario geben), so dass wir nicht einfach alle möglichen Werte von A und B (oder A, B, C usw.) vorbelegen können –

+0

Sie müssen nicht vorpoplate es.Sie können in diese Tabelle zusammenführen, und dann einfach lesen resultierenden Wert von LAST_SEQ.Aber dies kann nicht durch JQL, Hibernate unterstützt absichtlich nicht MERGE – ibre5041

+0

Sie können nicht Zeile Ebene Sperren Sie etwas, wenn es keine Zeile zum Sperren gibt. Was passiert also, wenn zwei Threads aus der Tabelle auswählen, keine Ergebnisse erhalten und dann versuchen, die erste Zeile gleichzeitig einzufügen? –

0

Sie möchten, dass Ihre Transaktionen dieses serializable lesen-inkrement-update/insert serialisieren (wie in "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"), dass die DB versucht, eine Reihenfolge zu finden, in der Transaktionen nacheinander ausgeführt würden und bekomme die gleichen Ergebnisse; Wenn es nicht möglich ist, können einige nicht passieren. Ich habe Ihr Szenario in Postgres CLI ausprobiert und es werden weder Einfügungen (Fehler bei Festschreibung) noch Konflikte bei Aktualisierungen (Fehler bei der zweiten Aktualisierung beendet, Transaktion abgebrochen) zugelassen. Ich denke Oracle verhält sich ähnlich, auch wenn Details variieren können. Das funktioniert auch dann, wenn das Kriterium für select in verschiedenen Transaktionen unterschiedlich ist: zum Beispiel die erste Transaktion wählt mit A, die zweite mit B, beide tun Änderungen und diese Änderungen würden irgendeine der vorherigen Auswahl beeinflussen - Sie erhalten einen Fehler. Und dann, denke ich, versuche es erneut. Achten Sie auf mögliche Deadlocks, die mit der SERIALIZABLE-Isolation verbunden sind, was zu Fehlern führen wird (aber Sie haben bereits Wiederholungen, das ist in Ordnung).

Übrigens, für diese Art der Aktualisierung Korrektheit REPEATABLE READ Isolationsstufe ist genug, aber nicht für Einsätze.

Wenn Sie verschiedene Transaktionsisolationsstufen in Hibernate verwenden möchten (wahrscheinlich möchten Sie nicht, dass alle Transaktionen serialisierbar sind, da dies die Leistung beeinflusst), kann ich nur zwei Konfigurationsfactorys aus zwei Konfigurationsdateien (oder manuell) konfigurieren im Code, oder aus einer Datei konfiguriert und Isolationsstufe im Code für eine von ihnen aktualisiert). Einer von ihnen wird eine "serialisierbare" Isolationsstufe haben und Sie würden ausschließlich mit Ihrem Tisch arbeiten. Die Eigenschaft für die Isolationsstufe lautet "hibernate.connection.isolation".

Im Frühjahr genügt es, eine Methode mit @Transactional(isolation=Isolation.SERIALIZABLE) zu kommentieren.

0

Hier ist eine SQL-Lösung für Sie, Sie können die native SQL-Schnittstelle von Hibernate zur Vereinfachung verwenden.

insert into myTable (A, B, LAST_SEQ) 
select 'T', 'Y', 
(
    case 
    when (select max(LAST_SEQ) from myTable where A = 'T' and B = 'Y') is null then 1 
    else (select 1 + max(LAST_SEQ) from myTable where A = 'T' and B = 'Y') 
    end 
); 

Ich hoffe, es hilft.

+0

Danke. Das könnte funktionieren, aber ich müsste eine Kennung zurückbekommen, nennen Sie sie LAST_SEQ, die mir sagen würde, wie ich die Zeile, die ich eingefügt habe, zurückgelesen bekomme. –