2017-10-05 3 views
2

Mit Postgres 9.6, ich habe die Strategie in https://stackoverflow.com/a/40325406/435563 empfohlen folgte ein INSERT oder SELECT und die daraus gewonnenen Ergebnisse id zu tun: nichtsINSERT oder SELECT-Strategie, um immer eine Zeile zurückzugeben?

with ins as (
    insert into prop (prop_type, norm, hash, symbols) 
    values (
    $1, $2, $3, $4 
) on conflict (hash) do 
    update set prop_type = 'jargon' where false 
    returning id) 
select id from ins 
union all 
select id from prop where hash = $3 

jedoch manchmal zurück. Ich hätte erwartet, dass es eine Zeile egal was zurückgeben würde. Wie kann ich es reparieren, um sicherzustellen, dass es immer eine ID zurückgibt?

NB, obwohl keine Zeile zurückgegeben wird, scheint die Zeile bei der Inspektion zu existieren. Ich glaube, dass das Problem damit verbunden sein kann, den gleichen Datensatz über zwei Sitzungen gleichzeitig hinzuzufügen.

Die betreffende Tabelle ist wie folgt definiert:

create table prop (
    id serial primary key, 
    prop_type text not null references prop_type(name), 
    norm text not null, 
    hash text not null unique, 
    symbols jsonb 
); 

Daten:

EDT DETAIL: parameters: $1 = 'jargon', $2 = 'j2', $3 = 'lXWkZSmoSE0mZ+n4xpWB', $4 = '[]' 

Wenn ich prop_type = 'jargon' ändern prop_type = 'foo' es funktioniert! Es scheint, dass die Sperre nicht genommen wird, wenn der Ausdruck selbst unter der where false Klausel nichts ändern würde. Muss das wirklich davon abhängen, dass ich einen Wert erraten habe, der nicht in der Reihe wäre? Oder gibt es einen besseren Weg, um sicherzustellen, dass Sie das Schloss bekommen?

--- UPDATE ---

Die Gesamtsituation ist, dass die Anwendung versucht, einen gerichteten azyklischen Graphen mit einem Verbindungspool (... mit autocommit) zu speichern, und wurde mit dieser Abfrage zu erhalten ID beim Duplizieren von Duplikaten. [Es stellt sich heraus, dass es viel klüger ist, eine Transaktion zu verwenden und nur zu einer Verbindung zu serialisieren. Aber das Verhalten, wenn Streit gibt es hier ungerade]

Der Fremdschlüssel scheint nicht um den Einsatz zu beeinflussen - zB.

create table foo(i int unique, prop_id int references prop(id)); 
insert into foo values (1, 208); 
insert into foo values (1, 208) 
on conflict (i) do update set prop_id = 208 where false; 
--> INSERT 0 0 
insert into foo values (1, 208) 
on conflict (i) do update set prop_id = -208 where false; 
--> INSERT 0 0 

Hinweis eines mit gültigen fk 208, das andere mit ungültigen -208. Wenn ich ein select auf eines dieser beiden Elemente mit dem vollständigen Muster verbinde, geben beide in Situationen ohne Konkurrenz i = 1 zurück, wie erwartet.

+0

bitte Datenabtastwert Post als auch, wenn im nicht irre es immer eine Zeile zurückgeben, so könnte man sogar von xmin sagen, xmax, wenn es wurde aktualisiert oder eingefügt –

+0

Daten hinzugefügt. Ich denke, es ist mit der Anwendung verwandt, die versucht, eine komplexe Datenstruktur zu speichern, in der dieses Blatt zweimal unter Verwendung eines Verbindungspools angezeigt wird. Der Datensatz wird also in zwei verschiedenen Verbindungen hinzugefügt. – shaunc

+0

mit Ihrem Update habe ich festgestellt, wo falsch, was Update nicht passieren - mit dieser Antwort wird anders sein :) –

Antwort

2

Ihre Beobachtung scheint unmöglich. Der obige Befehl sollte immer eine ID zurückgeben, entweder für die neu eingefügte Zeile oder für die bereits vorhandene Zeile. Gleichzeitige Schreibvorgänge können dies nicht stören, da vorhandene in Konflikt stehende Zeilen gesperrt sind.Erläuterung in dieser verwandten Antwort:

Sofern eine Ausnahme angehoben wird, natürlich. In diesem Fall erhalten Sie anstelle eines Ergebnisses eine Fehlermeldung. Hast du das überprüft? Haben Sie eine Fehlerbehandlung? (Falls Ihre App verwirft irgendwie Fehlermeldungen: 1), dass Fix. . 2) Es gibt einen zusätzlichen Eintrag in der DB Protokoll mit Standard-Protokolleinstellungen)

Ich habe eine FK-Einschränkung in der Tabelle Definition siehe:

prop_type text not null references prop_type(name), 

Wenn Sie versuchen, eine Zeile einzufügen das verletzt die Beschränkung, genau das passiert. Wenn es mit name = 'jargon' in Tabelle prop_type keine Zeile ist, ist das, was man bekommt:

ERROR: insert or update on table "prop" violates foreign key constraint "prop_prop_type_fkey" 
DETAIL: Key (prop_type)=(jargon) is not present in table "prop_type". 

Demo:

dbfiddle here

Ihre Beobachtung das Verbrechen passen würde:

Wenn ich prop_type = 'Jargon' ändern = 'foo' prop_type es funktioniert!

Aber Ihre Erklärung basiert auf falschen Vorstellungen:

Es würde die Sperre scheint nicht in Anspruch genommen, wenn der Ausdruck etwas nicht einmal die, wo falsche Klausel gegeben ändern würde.

So funktioniert Postgres nicht. Die Sperre wird so oder so (Erklärung in oben verlinkten Antwort) entnommen und die Postgres Verriegelungsmechanismus hält nicht einmal, wie sich die neue Zeile der alten vergleicht.

Muss das wirklich davon abhängen, dass ich einen Wert erraten habe, der nicht in der Reihe wäre? Oder gibt es einen besseren Weg, um sicherzustellen, dass Sie das Schloss bekommen?

Nr Und nein.

Wenn fehlende FK-Werte tatsächlich das Problem sind, können Sie fehlende (eindeutige) Werte in einer einzigen Anweisung mit rCTEs hinzufügen. Einfach für einreihige Inserts, wie Sie es vorführen, funktioniert aber auch, um viele Zeilen gleichzeitig einzufügen.Verwandte:

+0

danke für die Antwort. Mit Ihrer Bestätigung scheint es unwahrscheinlich. In der Tat ist "Jargon" in "prop_type", während "foo" nicht ist. Ich habe in der Zwischenzeit auf Serialisierung mit einem einzigen Client für den gesamten Graphen von Objekten umgeschaltet, anstatt mich auf einen Pool zu verlassen, was bedeutet, dass der Fehler nicht reproduziert, sondern versuchen wird, ob ich zurückgehen kann, da es sehr merkwürdig erschien mir. – shaunc

+0

@shaunc: Du meinst das anders herum? 'foo' in' prop_type' und * nicht * 'Jargon'? Denn wenn 'foo' * nicht * wäre, würde das anfangen * wirklich * komisch zu werden ... –

+0

Das ist was ich meine (!) Allerdings kann ich jetzt nicht auf die andere Möglichkeit eingehen, die du erwähnt hast - dass da war ein Fehler, den ich irgendwie vermisste (obwohl sie in die App geworfen werden, wenn sie nicht erwischt werden, und mein Testanwendungsprotokoll hat keine Informationen; ich kann jedoch jetzt nicht in pg_log finden). Also lehne ich mich dieser Option zu, da ich weiß, dass es ziemlich schwierig ist, echte Fehler zu finden. Wie gesagt, ich kann mit dem aktuellen Code nicht reproduzieren, aber es ist seltsam genug, dass ich zurückgehen und es versuchen möchte. Wenn es keinen Fehler gibt, welchen Beweis sollte ich dann versuchen, um zu bestätigen, dass etwas Seltsames passiert? – shaunc

1

https://www.postgresql.org/docs/9.5/static/sql-insert.html

ON CONFLICT aktualisiere garantiert eine Atom INSERT oder UPDATE Ergebnis; vorausgesetzt, es gibt keinen unabhängigen Fehler, ist eines dieser beiden Ergebnisse garantiert, auch bei hoher Parallelität.

Dies ist in Bezug auf die Sperre Erwähnung in Ihrem aktualisierten Beitrag. Jetzt bezüglich der Anfangsfrage mit der wiederkehrenden Reihe - ich habe es zuerst unangemessen gelesen. Jetzt, wo ich die where false sah - mit dieser Klausel nicht immer haben Sie eine Zeile zurückgegeben. zB:

t=# create table a(i int, e int); 
CREATE TABLE 
t=# insert into a select 1,1; 
INSERT 0 1 
t=# create unique index b on a (i); 
CREATE INDEX 
---now insert on conflict do nothing: 
t=# insert into a select 1,1 on conflict do nothing returning *,xmax,xmin; 
i | e | xmax | xmin 
---+---+------+------ 
(0 rows) 

INSERT 0 0 
-- where false same effect - no rows 
t=# insert into a select 1,1 on conflict(i) do update set e=2 where false returning *,xmax,xmin; 
i | e | xmax | xmin 
---+---+------+------ 
(0 rows) 
-- now insert without conflict: 
t=# insert into a select 2,2 on conflict(i) do update set e=2 where EXCLUDED.e=1 returning *,xmax; 
i | e | xmax 
---+---+------ 
2 | 2 | 0 
(1 row) 
-- now insert with update on conflict: 
INSERT 0 1 
t=# insert into a select 1,1 on conflict(i) do update set e=2 where EXCLUDED.e=1 returning *,xmax; 
i | e | xmax 
---+---+----------- 
1 | 2 | 126943767 
(1 row) 
+0

Das Intro zu der Frage ist irreführend, es geht eigentlich um 'INSERT' oder' SELECT', nicht 'INSERT' oder' UPDATE'. Und Sie haben möglicherweise die angehängte 'UNION ALL SELECT ...' in der Frage übersehen. –

+0

@ErwinBrandstetter danke - Tatsächlich habe ich falsche Frage beantwortet :) –

Verwandte Themen