La mia funzione new_customer
viene chiamata più volte al secondo (ma solo una volta per sessione) da un'applicazione web. La prima cosa che fa è bloccare la customer
tabella (per fare un 'inserire se non esiste', una semplice variante di un upsert
).
La mia comprensione dei documenti è che le altre chiamate a new_customer
dovrebbero semplicemente fare la coda fino al termine di tutte le chiamate precedenti:
LOCK TABLE ottiene un blocco a livello di tabella, attendendo se necessario il rilascio di eventuali blocchi in conflitto.
Perché a volte è invece deadlock?
definizione:
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;
$$;
errore dal registro:
2015-07-28 08:02:58 DETTAGLIO BST: il processo 12380 attende l'esclusivo blocco sulla relazione 16438 del database 12141; bloccato dal processo 12379. Il processo 12379 attende ExclusiveLock sulla relazione 16438 del database 12141; bloccato dal processo 12380. Processo 12380: seleziona new_customer (decodifica ($ 1 :: testo, 'hex')) Processo 12379: seleziona new_customer (decodifica ($ 1 :: testo, 'hex')) 2015-07-28 08:02:58 BST SUGGERIMENTO: consultare il registro del server per i dettagli della query. 2015-07-28 08:02:58 BST CONTESTO: istruzione "new_customer" della funzione SQL 1 28/07/2015 08:02:58 DICHIARAZIONE BST: selezionare new_customer (decodifica ($ 1 :: testo, 'hex'))
relazione:
postgres=# select relname from pg_class where oid=16438;
┌──────────┐
│ relname │
├──────────┤
│ customer │
└──────────┘
modificare:
Sono riuscito a ottenere un caso di test riproducibile semplice. Per me questo sembra un bug a causa di una sorta di condizione di razza.
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;
$$;
script bash eseguito contemporaneamente in due sessioni bash:
for i in {1..1000}; do psql postgres postgres -c "select f_test('blah')"; done
registro errori (di solito una manciata di deadlock oltre le 1000 chiamate):
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')
modifica 2:
@ypercube ha suggerito una variante con l' lock table
esterno della funzione:
for i in {1..1000}; do psql postgres postgres -c "begin; lock test in exclusive mode; select f_test('blah'); end"; done
interessante notare che questo elimina i deadlock.
customer
utilizzato in modo da afferrare un blocco più debole? Quindi potrebbe essere un problema di aggiornamento del blocco.