2011-08-05 8 views
14

tenemos algunos datos persistentes en una aplicación, que se consulta desde un servidor y luego se almacena en una base de datos para que podamos seguir la pista de información adicional. Como no queremos consultar cuando se usa un objeto en la memoria, hacemos un select for update para que otros hilos que quieran obtener los mismos datos sean bloqueados.Tiene el bloque "seleccionar para actualizar" en filas no existentes

No estoy seguro de cómo select for update maneja las filas que no existen. Si la fila no existe y otro subproceso intenta hacer otra select for update en la misma fila, ¿se bloqueará este subproceso hasta que la otra transacción finalice o también obtendrá un conjunto de resultados vacío? Si solo obtiene un conjunto de resultados vacío, ¿hay alguna forma de bloquearlo también, por ejemplo, insertando la fila que falta inmediatamente?

EDIT:

Debido a que hubo una observación, que puede producir un bloqueo tanto, he aquí algunos detalles más sobre el uso de concreto en nuestro caso. En pseudocódigo reducida nuestro flujo Programm se ve así:

d = queue.fetch(); 
r = SELECT * FROM table WHERE key = d.key() FOR UPDATE; 
if r.empty() then 
    r = get_data_from_somewhere_else(); 

new_r = process_stuff(r); 


if Data was present then 
    update row to new_r 
else 
    insert new_r 

Este código se ejecuta en múltiples hilos y los datos que se obtiene de la cola podría estar en relación con la misma fila en la base de datos (de ahí el bloqueo). Sin embargo, si varios hilos están usando datos que necesitan la misma fila, entonces estos hilos deben ser secuenciados (el orden no importa). Sin embargo, esta secuenciación falla, si la fila no está presente, porque no obtenemos un bloqueo.

EDIT:

Por ahora tengo la siguiente solución, que parece un truco feo para mí.

select the data for update 
if zero rows match then 
    insert some dummy data // this will block if multiple transactions try to insert 
    if insertion failed then 
    // somebody beat us at the race 
    select the data for update 

do processing 

if data was changed then 
    update the old or dummy data 
else 
    rollback the whole transaction 

No estoy ni 100% seguro de que esto realmente resuelva el problema, ni esta solución parece un buen estilo. Entonces, si alguien tiene que ofrecer algo más utilizable, sería genial.

+0

En relación con su edición: ¿Dónde obtiene un candado? –

+0

@Catcall: Ups, olvidé "PARA ACTUALIZAR" en la instrucción de selección. Entonces este es el lugar donde se toma la cerradura. – LiKao

Respuesta

15

No estoy seguro de cómo seleccionar para la actualización maneja las filas no existentes.

No lo es.

Lo mejor que puede hacer es usar un bloqueo de aviso si conoce algo único sobre la nueva fila. (Utilice hashtext() si es necesario, y el oid de la tabla para bloquearlo).

Lo mejor es un bloqueo de tabla.

Habiendo dicho eso, su pregunta parece que está bloqueando mucho más de lo que debería. Solo bloquee las filas cuando realmente las necesite, es decir, operaciones de escritura.

+0

Gracias por la pista sobre bloqueos de aviso. Desde el primer vistazo, parece útil, pero soy un poco escéptico porque la documentación dice que los bloqueos de aviso no están vinculados a las transacciones, por lo que debemos asegurarnos de desbloquearlos (y si el programa falla). Además, no creo que estemos bloqueados demasiado (ver edición). – LiKao

+0

Eso es correcto, pero verifique Postgres 9.1: introduce bloqueos de aviso de nivel de transacción que se liberan automáticamente en la confirmación o reversión. –

+0

Bueno saber. Esto significa que podemos reducir mucha lógica cuando cambiamos a 9.1. Por ahora, lamentablemente estamos atascados en 8.4 :(. – LiKao

0

Al mirar el código agregado en la segunda edición, se ve bien.

En cuanto a que se ve como un truco, hay un par de opciones: básicamente se trata de mover la lógica de la base de datos a la base de datos.

Una es simplemente poner el conjunto seleccione para la actualización, si no existe entonces insertar lógica en una función, y hacer select get_object(key1,key2,etc) lugar.

Como alternativa, puede hacer que un desencadenador de inserción ignore los intentos de agregar una entrada si ya existe, y simplemente haga una inserción antes de hacer la selección para la actualización. Sin embargo, esto tiene más potencial para interferir con otros códigos que ya están en su lugar.

(Si no recuerdo a, voy a editar y añadir código de ejemplo más adelante, cuando estoy en una posición para comprobar lo que estoy haciendo.)

+0

La primera sugerencia es en realidad incorrecta: si agrega una declaración de suspensión antes del inserto en su pseudo código (que aterrizaría como pg_sleep () en la función SQL), se producirá un error (tecla dup en lo que sea único). El manejo de excepciones en el nivel SQL tampoco se bloqueará, pero no saldrá el error. La edición actual del OP hará el trabajo, aunque –

+0

En realidad, me estaba refiriendo a la segunda edición, la de "parece una piratería". Esto probablemente no fue obvio si no lo vinculaste a la siguiente oración. – MaHuJa

0

solución de Ejemplo (no he encontrado mejor : /)

Tema A:

BEGIN; 
SELECT pg_advisory_xact_lock(42); -- database semaphore arbitrary ID 
SELECT * FROM t WHERE id = 1; 
DELETE FROM t WHERE id = 1; 
INSERT INTO t (id, value) VALUES (1, 'thread A'); 
SELECT 1 FROM pg_sleep(10); -- only for race condition simulation 
COMMIT; 

Tema B:

BEGIN; 
SELECT pg_advisory_xact_lock(42); -- database semaphore arbitrary ID 
SELECT * FROM t WHERE id = 1; 
DELETE FROM t WHERE id = 1; 
INSERT INTO t (id, value) VALUES (1, 'thread B'); 
SELECT 1 FROM pg_sleep(10); -- only for race condition simulation 
COMMIT; 

Las causas siempre corrigen el orden de ejecución de las transacciones.

Cuestiones relacionadas