new_customer
Una aplicación web llama a mi función varias veces por segundo (pero solo una vez por sesión). Lo primero que hace es bloquear la customer
tabla (hacer un 'insertar si no existe', una variante simple de un upsert
).
Entiendo que los documentos son que otras llamadas new_customer
simplemente deberían hacer cola hasta que todas las llamadas anteriores hayan terminado:
LOCK TABLE obtiene un bloqueo a nivel de tabla, esperando si es necesario para liberar cualquier bloqueo en conflicto.
¿Por qué a veces es un punto muerto?
definición:
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;
$$;
error del registro:
2015-07-28 08:02:58 BST DETALLE: El proceso 12380 espera a ExclusiveLock en la relación 16438 de la base de datos 12141; bloqueado por el proceso 12379. El proceso 12379 espera a ExclusiveLock en la relación 16438 de la base de datos 12141; bloqueado por el proceso 12380. Proceso 12380: seleccione new_customer (decode ($ 1 :: text, 'hex')) Proceso 12379: seleccione new_customer (decode ($ 1 :: text, 'hex')) 2015-07-28 08:02:58 BST SUGERENCIA: Consulte el registro del servidor para obtener detalles de la consulta. 2015-07-28 08:02:58 BST CONTEXTO: función SQL "nuevo_cliente" declaración 1 2015-07-28 08:02:58 BST STATEMENT: select new_customer (decode ($ 1 :: text, 'hex'))
relación:
postgres=# select relname from pg_class where oid=16438;
┌──────────┐
│ relname │
├──────────┤
│ customer │
└──────────┘
editar:
Me las arreglé para obtener un caso de prueba reproducible simple. Para mí, esto parece un error debido a algún tipo de condición de carrera.
esquema:
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;
$$;
El script bash se ejecuta simultáneamente en dos sesiones bash:
for i in {1..1000}; do psql postgres postgres -c "select f_test('blah')"; done
registro de errores (generalmente un puñado de puntos muertos en las 1000 llamadas):
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')
editar 2:
@ypercube sugirió una variante con el lock table
exterior de la función:
for i in {1..1000}; do psql postgres postgres -c "begin; lock test in exclusive mode; select f_test('blah'); end"; done
Curiosamente, esto elimina los puntos muertos.
customer
usa de una manera que agarre un bloqueo más débil? Entonces podría ser un problema de actualización de bloqueo.