Ma fonction new_customer
est appelée plusieurs fois par seconde (mais seulement une fois par session) par une application web. La toute première chose qu'il fait est de verrouiller la customer
table (pour faire un «insert s'il n'existe pas» - une variante simple d'un upsert
).
Ma compréhension des documents est que les autres appels vers new_customer
doivent simplement faire la queue jusqu'à ce que tous les appels précédents soient terminés:
LOCK TABLE obtient un verrou au niveau de la table, attendant si nécessaire que les verrous en conflit soient libérés.
Pourquoi est-il parfois dans l'impasse à la place?
définition:
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;
$$;
erreur du journal:
2015-07-28 08:02:58 BST DETAIL: Le processus 12380 attend ExclusiveLock sur la relation 16438 de la base de données 12141; bloqué par le processus 12379. Le processus 12379 attend ExclusiveLock sur la relation 16438 de la base de données 12141; bloqué par le processus 12380. Processus 12380: sélectionnez new_customer (décode ($ 1 :: text, 'hex')) Processus 12379: sélectionnez new_customer (décode ($ 1 :: text, 'hex')) 2015-07-28 08:02:58 CONSEIL BST: Voir le journal du serveur pour les détails de la requête. 2015-07-28 08:02:58 BST CONTEXT: instruction "new_customer" de la fonction SQL 1 2015-07-28 08:02:58 BST STATEMENT: select new_customer (decode ($ 1 :: text, 'hex'))
relation:
postgres=# select relname from pg_class where oid=16438;
┌──────────┐
│ relname │
├──────────┤
│ customer │
└──────────┘
Éditer:
J'ai réussi à obtenir un cas de test reproductible simple. Pour moi, cela ressemble à un bug en raison d'une sorte de condition de concurrence.
schéma:
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 exécuté simultanément dans deux sessions bash:
for i in {1..1000}; do psql postgres postgres -c "select f_test('blah')"; done
journal des erreurs (généralement une poignée de blocages sur les 1000 appels):
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')
modifier 2:
@ypercube a proposé une variante avec l' lock table
extérieur de la fonction:
for i in {1..1000}; do psql postgres postgres -c "begin; lock test in exclusive mode; select f_test('blah'); end"; done
fait intéressant, cela élimine les blocages.
customer
utilisé de manière à saisir un verrou plus faible? Cela pourrait alors être un problème de mise à niveau du verrou.