Meine Funktion new_customer
wird mehrmals pro Sekunde (jedoch nur einmal pro Sitzung) von einer Webanwendung aufgerufen. Als erstes wird die customer
Tabelle gesperrt (Einfügen, wenn nicht vorhanden upsert
).
Nach meinem Verständnis der Dokumentationnew_customer
sollten andere Anrufe einfach anstehen, bis alle vorherigen Anrufe beendet sind:
LOCK TABLE ruft eine Sperre auf Tabellenebene ab und wartet bei Bedarf darauf, dass widersprüchliche Sperren freigegeben werden.
Warum ist es stattdessen manchmal Deadlocking?
Definition:
create function new_customer(secret bytea) returns integer language sql
security definer set search_path = postgres,pg_temp as $$
lock customer in exclusive mode;
--
with w as ( insert into customer(customer_secret,customer_read_secret)
select secret,decode(md5(encode(secret, 'hex')),'hex')
where not exists(select * from customer where customer_secret=secret)
returning customer_id )
insert into collection(customer_id) select customer_id from w;
--
select customer_id from customer where customer_secret=secret;
$$;
Fehler aus dem Protokoll:
2015-07-28 08:02:58 BST DETAIL: Prozess 12380 wartet auf ExclusiveLock für Relation 16438 der Datenbank 12141; gesperrt durch Prozess 12379. Der Prozess 12379 wartet auf ExclusiveLock für die Beziehung 16438 der Datenbank 12141. durch den Prozess 12380 blockiert. Prozess 12380: new_customer auswählen (decodieren ($ 1 :: text, 'hex')) Prozess 12379: new_customer auswählen (decodieren ($ 1 :: text, 'hex')) 2015-07-28 08:02:58 BST TIPP: Weitere Informationen zu Abfragen finden Sie im Serverprotokoll. 2015-07-28 08:02:58 BST CONTEXT: SQL-Funktionsanweisung "new_customer" 1 2015-07-28 08:02:58 BST STATEMENT: new_customer auswählen (decodieren ($ 1 :: text, 'hex'))
Beziehung:
postgres=# select relname from pg_class where oid=16438;
┌──────────┐
│ relname │
├──────────┤
│ customer │
└──────────┘
bearbeiten:
Ich habe es geschafft, einen einfach reproduzierbaren Testfall zu bekommen. Für mich sieht das nach einem Fehler aus, der auf eine Art Rennbedingung zurückzuführen ist.
Schema:
create table test( id serial primary key, val text );
create function f_test(v text) returns integer language sql security definer set search_path = postgres,pg_temp as $$
lock test in exclusive mode;
insert into test(val) select v where not exists(select * from test where val=v);
select id from test where val=v;
$$;
Bash-Skript wird gleichzeitig in zwei Bash-Sitzungen ausgeführt:
for i in {1..1000}; do psql postgres postgres -c "select f_test('blah')"; done
Fehlerprotokoll (normalerweise eine Handvoll Deadlocks über die 1000 Aufrufe):
2015-07-28 16:46:19 BST ERROR: deadlock detected
2015-07-28 16:46:19 BST DETAIL: Process 9394 waits for ExclusiveLock on relation 65605 of database 12141; blocked by process 9393.
Process 9393 waits for ExclusiveLock on relation 65605 of database 12141; blocked by process 9394.
Process 9394: select f_test('blah')
Process 9393: select f_test('blah')
2015-07-28 16:46:19 BST HINT: See server log for query details.
2015-07-28 16:46:19 BST CONTEXT: SQL function "f_test" statement 1
2015-07-28 16:46:19 BST STATEMENT: select f_test('blah')
2 bearbeiten:
@ypercube schlug eine Variante mit lock table
der Funktion außerhalb vor:
for i in {1..1000}; do psql postgres postgres -c "begin; lock test in exclusive mode; select f_test('blah'); end"; done
Interessanterweise beseitigt dies die Deadlocks.
quelle
customer
eine Methode verwendet, die eine schwächere Sperre aufhebt? Dann könnte es ein Problem mit dem Upgrade der Sperre sein.Antworten:
Ich habe dies auf pgsql-bugs gepostet und die Antwort von Tom Lane zeigt an, dass es sich um ein Problem mit der Sperreneskalation handelt, das durch die Mechanik der Verarbeitung von SQL-Sprachfunktionen verschleiert ist. Im Wesentlichen wird die von generierte Sperre vor der exklusiven Sperre für die Tabelle
insert
abgerufen :Dies erklärt auch, warum das Sperren der Tabelle außerhalb der Funktion in einem umhüllenden plpgsql-Block (wie von @ypercube vorgeschlagen) die Deadlocks verhindert.
quelle
Angenommen, Sie führen vor dem Aufruf von new_customer eine andere Anweisung aus, und diese Anweisungen erhalten eine Sperre, die im Widerspruch zu
EXCLUSIVE
(im Grunde genommen jede Datenänderung in der Kundentabelle ) steht, ist die Erklärung sehr einfach.Man kann das Problem mit einem einfachen Beispiel reproduzieren (das nicht einmal eine Funktion enthält):
1. Sitzung:
2. Sitzung
1. Sitzung
Wenn die erste Sitzung das Einfügen ausführt, erhält sie die
ROW EXCLUSIVE
Sperre für eine Tabelle. Währenddessen ruft Sitzung 2 auch dieROW EXCLUSIVE
Sperre ab und versucht, eineEXCLUSIVE
Sperre zu erwerben . An diesem Punkt muss auf die erste Sitzung gewartet werden, da dieEXCLUSIVE
Sperre mit in Konflikt stehtROW EXCLUSIVE
. Endlich springt die erste Sitzung über die Haie und versucht, eineEXCLUSIVE
Sperre zu erhalten. Da die Sperren jedoch in der richtigen Reihenfolge erworben wurden, wird sie nach der zweiten Sitzung in die Warteschlange gestellt. Dies wiederum wartet auf den ersten, was zu einem Deadlock führt:Die Lösung für dieses Problem besteht darin, Sperren so früh wie möglich zu erwerben, normalerweise als erstes in einer Transaktion. Auf der anderen Seite benötigt die PostgreSQL-Workload nur in sehr seltenen Fällen Sperren. Ich empfehle daher, die Vorgehensweise beim Upsert zu überdenken (siehe Artikel unter http://www.depesz.com/2012/06/10) / warum-ist-so-kompliziert / ).
quelle
Process 28514 : select new_customer(decode($1::text, 'hex')); Process 28084 : BEGIN; INSERT INTO test VALUES(1); select new_customer(decode($1::text, 'hex'))
Während Jack gerade Folgendes bekam:Process 12380: select new_customer(decode($1::text, 'hex')) Process 12379: select new_customer(decode($1::text, 'hex'))
- Zeigt an, dass der Funktionsaufruf der erste Befehl in beiden Transaktionen ist (es sei denn, mir fehlt etwas).