2014-04-23 7 views
6

Ich habe diese Abfrage, die versucht, Zeilen in die balances Tabelle hinzuzufügen, wenn keine entsprechende Zeile in der totals Tabelle vorhanden ist. Die Abfrage wird in einer Transaktion ausgeführt, die die Standardisolationsstufe für PostgreSQL verwendet.Kann eine INSERT-SELECT-Abfrage den Rennbedingungen unterliegen?

INSERT INTO balances (account_id, currency, amount) 
SELECT t.account_id, t.currency, 0 
FROM balances AS b 
RIGHT OUTER JOIN totals USING (account_id, currency) AS t 
WHERE b.id IS NULL 

Ich habe eine UNIQUE Einschränkung für balances (accountId, currency). Ich bin besorgt, dass ich in eine Race-Condition-Situation geraten werde, die zu doppelten Schlüsselfehlern führt, wenn mehrere Sessions diese Abfrage gleichzeitig ausführen. Ich habe viele Fragen zu diesem Thema gesehen, aber alle scheinen entweder Unterabfragen, mehrere Abfragen oder pgSQL-Funktionen zu beinhalten.

Da ich keine von denen in meiner Abfrage verwende, ist es frei von Race Conditions? Wenn es nicht ist, wie kann ich es reparieren?

+0

Ja, Sie doppelte Schlüssel Fehler erhalten können erneut zu versuchen. Aus dem gleichen Grund können Sie es erhalten, wenn zwei Sitzungen die gleiche 'insert'-Anweisung mit derselben' values'-Klausel ausführen. Die Anweisung erkennt während der Ausführung einen konsistenten Status der Datenbank. Daher werden keine neuen Zeilen angezeigt, selbst wenn sie während der Ausführung der Anweisung festgeschrieben wurden. –

+0

Eine Plpgsql-Funktion, die im Falle einer doppelten Schlüsselverletzung eine Schleife bildet, kann mit der Race-Bedingung auf der Serverseite und bei der Standard-Isolationsstufe umgehen, die * sicher * und typischerweise * das billigste * ist. Die App muss sich nicht mit Wiederholungen befassen: http://stackoverflow.com/questions/15939902/is-select-or-insert-in-a-function-prone-to-race-conditions/15950324#15950324 –

Antwort

3

Ja, es wird mit einem duplicate key value violates unique constraint Fehler fehlschlagen . Was ich tue ist, den Einfüge-Code in einen try/except Block zu legen und wenn die Ausnahme ausgelöst wird, fange ich es und versuche es erneut. So einfach. Wenn die Anwendung nicht über eine große Anzahl von Benutzern verfügt, funktioniert sie einwandfrei.

In Ihrer Abfrage ist die Standardisolationsstufe ausreichend, da es sich um eine einzelne INSERT-Anweisung handelt und kein Risiko für Phantomlesevorgänge besteht.

Beachten Sie, dass der try/except-Block nicht vermeidbar ist, selbst wenn die Isolationsstufe serialisiert wird. From the manual about serializable:

wie die Repeatable Read Ebene müssen Anwendungen auf diese Ebene unter Verwendung vorbereitet werden, um Transaktionen Ausfälle durch Serialisierung noch

+0

Ich bevorzuge das Wiederholen, anstatt die Transaktionsebene auf serialisierbar zu setzen. Es sollte kein Problem sein, selbst bei vielen Benutzern, da die Menge der Zeilen, die fehlen könnte, begrenzt ist, so dass die Abfrage meistens ein NOP ist. – LordOfThePigs

+0

@Lord Es ist nicht serialisierbar vs Wiederholung. Überprüfen Sie das Update. –

+0

Wenn ich den Vorgang wiederholen muss, möchte ich die Transaktionsisolationsstufe lieber nicht ändern. Ich wünschte, ich könnte beide Antworten akzeptieren ... – LordOfThePigs

1

Die Standardtransaktionsebene lautet Read Committed. In dieser Ebene sind Phantom-Lesevorgänge möglich (siehe Table 13.1). Während Sie davor geschützt sind, in der Summentabelle merkwürdige Effekte zu sehen, wenn Sie die Summen aktualisieren, sind Sie nicht vor Phantom-Lesevorgängen in der Salden-Tabelle geschützt.

Was dies bedeutet, kann erklärt werden, wenn Sie eine einzelne Abfrage ähnlich wie Ihre suchen, die den äußeren Join zweimal versucht (und nur Abfragen, fügt nichts ein). Die Tatsache, dass ein Saldo fehlt, ist nicht garantiert, dass er zwischen den beiden "Peeks" in der Saldenliste gleich bleibt. Das plötzliche Auftreten eines Saldos, das nicht vorhanden war, als die gleiche Transaktion zum ersten Mal aussah, wird als "Phantom Read" bezeichnet.

In Ihrem Fall können mehrere gleichzeitige Anweisungen sehen, dass ein Kontostand fehlt, und nichts hindert sie daran, es einzufügen und Fehler auszuschließen.

Um auszuschließen, Phantom liest (und Ihre Abfrage zu beheben), können Sie in der SERIALIZABLE Isolationsstufe ausführen in müssen vor Ihrer Abfrage ausgeführt wird:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

+0

Das ist was ich dachte. Danke, dass du das bestätigt hast.Ich habe einen Teil meiner Frage hinzugefügt: Wie kann ich meine Anfrage reparieren? – LordOfThePigs

+1

Eine einzelne Anweisung unterliegt keinen Phantom-Lesevorgängen. Phantom-Lesevorgänge können nur stattfinden, wenn Sie in einer einzigen Transaktion nacheinander zwei Anweisungen ausführen. Bei einer einzigen INSERT-Anweisung können Phantom-Lesevorgänge selbst dann nicht ausgeführt werden, wenn sie auf einer SELECT-Anweisung basieren (die Anweisung erkennt einen konsistenten Zustand der Datenbank, während sie ausgeführt wird). Was * passieren * ist, dass während der Ausführung der Einfügung andere Transaktionen Werte einfügen, die zu einer PK-Verletzung führen, die aber nicht durch eine Phantom-Lese verursacht wird. –

+0

@a_horse_with_no_name - Ich kenne keine speziellen Regeln für einzelne Anweisungen, die wiederholt in derselben Tabelle vorkommen (sie können, soweit ich das aus der Dokumentation sehen kann, in beliebige Ausführungspläne kompilieren). Ich stimme zu, dass es sich bei dieser Frage nicht direkt um ein Phantom-Leseszenario handelt. Was ich sagen will ist, dass das Standard-Transaktionslevel nicht ausreicht - es schützt die Salden-Tabelle nicht durch eine Tabellensperre. –