Die im vorletzten Artikel vorgestellte Lösung hat den Nachteil, den ganzen Table my_table
zu locken.
Das ist dann ein Problem, wenn criterion
mittels unique constraint eindeutig gemacht wird und nach der Funktion eine zeitaufwendige Funktion (z.B. eine Suche) in derselben Transaktion folgt: Dann warten nämlich alle Prozesse, die ein findOrCreate
machen wollen, unabhängig mit welchem Wert für criterion
, auf den Abschluss der ersten Transaktion, da erst zu diesem Zeitpunkt sichergestellt werden kann, dass die Spalte wirklich unique ist.
Eigentlich sollte es aber möglich sein, dass parallele Transaktionen mit anderen Werten für criterion
unbehelligt ausgeführt werden.
Mit dem folgenden, von Martin Heistermann und Stephan Lüderitz vorgeschlagenen Ansatz, umgeht man dieses Problem elegant:
CREATE OR REPLACE FUNCTION findOrCreate( Â IN criterion_in text, Â OUT record_id integer, OUT is_new bool) AS $$ DECLARE BEGIN Â Â INSERT INTO Â Â Â my_table (criterion) Â Â VALUES Â Â Â (criterion_in) Â Â RETURNING id INTO record_id; Â Â SELECT true INTO is_new; EXCEPTION Â Â WHEN unique_violation THEN Â Â Â Â SELECT false, id into is_new, record_id FROM my_table WHERE criterion=criterion_in; END $$ language plpgsql;
Wie in dieser Präsentation (PDF) beschrieben, läßt PostgreSQL das parallele Einfügen verschiedener Werte mittels findOrCreate
zu, zwingt aber bei zwei Transaktionen, die denselben Wert einfügen wollen, die zweite, mit dem Insert auf das Ende der ersten zu warten.
Findet die (potentiell teure) Suche in der Transaktion nach findOrCreate statt, wird diese gar nicht mehr ausgeführt, da findOrCreate per Exception die von der ersten Transaktion neu eingefügte Zeile ermittelt und gleich mit deren Ergebnissen weitermachen kann.