2009-10-05 19 views
12

Tenemos una aplicación basada en web. Hay operaciones de base de datos con plazos (INSERTs y UPDATE) en la aplicación que tardan más en completarse, por lo que este flujo particular se ha cambiado a Java Thread para que no espere (bloquee) para que se complete la operación completa de la base de datos.Error de interbloqueo en la instrucción INSERT

Mi problema es, si hay más de 1 usuario llega a través de este flujo en particular, que se me presentan el siguiente error lanzada por PostgreSQL:

org.postgresql.util.PSQLException: ERROR: deadlock detected 
    Detail: Process 13560 waits for ShareLock on transaction 3147316424; blocked by process 13566. 
Process 13566 waits for ShareLock on transaction 3147316408; blocked by process 13560. 

El error anterior se tira constantemente en las instrucciones INSERT.

Información adicional: 1) Tengo la CLAVE PRIMARIA definida en esta tabla. 2) Hay referencias de LLAVE EXTRAÑA en esta tabla. 3) Se pasa una conexión de base de datos separada a cada subproceso de Java.

Tecnologías Servidor Web: Tomcat v6.0.10 Java Servlet v1.6.0 Base de datos: PostgreSQL v8.2.3 gestión de conexión: pgpool II

+0

La información adicional sería útil para diagnosticar su problema. ¿Podría proporcionar restricciones de clave foránea ESPECÍFICAS, alguna información básica sobre el esquema de la tabla y las sentencias SQL reales que causan el interbloqueo? –

Respuesta

0

Su problema, probablemente, es el comando de inserción está tratando para bloquear uno o ambos índices y los índices están bloqueados para la otra banda de rodadura.

Un error común es bloquear recursos en diferente orden en cada hilo. Verifique los pedidos e intente bloquear los recursos en el mismo orden en todos los hilos.

+1

PostgreSQL no bloquea los índices durante las declaraciones regulares, solo las filas en la tabla. Para bloquear un índice, deberá modificarlo o mantenerlo específicamente. –

5

punto muerto explicó:
En pocas palabras, lo que está sucediendo es que una determinada sentencia de SQL (INSERT u otro) está esperando en otra declaración para liberar un bloqueo en una parte específica de la base de datos, antes de que pueda proceder . Hasta que se libere este bloqueo, la primera instrucción SQL, llámala "declaración A", no se permitirá acceder a esta parte de la base de datos para hacer su trabajo (= situación de bloqueo regular). Pero ... la declaración A también ha puesto un candado en otra parte de la base de datos para garantizar que ningún otro usuario de la base de datos tenga acceso (para leer, modificar o eliminar, dependiendo del tipo de bloqueo). Ahora ... la segunda instrucción SQL, necesita acceder a la sección de datos marcada por el bloqueo de la declaración A. Eso es un BLOQUEO MUERTO: ambas declaraciones esperarán, infinito el uno al otro.

El remedio ...

Para ello sería necesario conocer la sentencia SQL específica estos diversos hilos se están ejecutando, y mirando en allí si hay una manera de cualquiera:

 
a) removing some of the locks, or changing their types. 
    For example, maybe the whole table is locked, whereby only a given row, or 
    a page thereof would be necessary. 
b) preventing multiple of these queries to be submitted at a given time. 
    This would be done by way of semaphores/locks (aka MUTEX) at the level of the 
    multi-threading logic. 

Beware que el enfoque "b)", si no se implementa correctamente, puede mover la situación de interbloqueo desde SQL dentro de la lógica de programa/subprocesos. La clave sería crear solo un mutex para ser obtenido primero por cualquier hilo que esté a punto de ejecutar una de estas consultas propensas a interbloqueos.

21

Una forma de hacer frente a los interbloqueos es tener un mecanismo de reintento que espera un intervalo aleatorio e intenta ejecutar la transacción nuevamente. El intervalo aleatorio es necesario para que las transacciones colisionantes no se sigan golpeando continuamente entre sí, lo que provoca lo que se denomina bloqueo en tiempo real, algo aún más desagradable de depurar.En realidad, la mayoría de las aplicaciones complejas necesitarán tal mecanismo de reintento tarde o temprano cuando necesiten manejar fallas de serialización de transacciones.

Por supuesto, si puede determinar la causa del punto muerto, generalmente es mucho mejor eliminarlo o va a volver para morderlo. Para casi todos los casos, incluso cuando la condición de interbloqueo es rara, vale la pena el pequeño rendimiento y la sobrecarga de codificación para obtener los bloqueos en orden determinista u obtener bloqueos de grano grueso para evitar el gran impacto de latencia ocasional y el acantilado de rendimiento repentino cuando escala la concurrencia.

Cuando consigues constantemente dos bloqueos de sentencia INSERT, lo más probable es que sea un problema exclusivo de orden de inserción de índice. Pruebe por ejemplo el siguiente en dos ventanas de comandos psql:

Thread A   | Thread B 
BEGIN;    | BEGIN; 
        | INSERT uniq=1; 
INSERT uniq=2;  | 
        | INSERT uniq=2; 
        | block waiting for thread A to commit or rollback, to 
        | see if this is an unique key error. 
INSERT uniq=1;  | 
    blocks waiting | 
    for thread B, | 
    DEADLOCK  | 
        V  

Por lo general, el mejor curso de acción para resolver este es averiguar los objetos padre que guardan todas esas transacciones. La mayoría de las aplicaciones tienen una o dos entidades principales, como usuarios o cuentas, que son buenos candidatos para esto. Entonces, todo lo que necesita es que cada transacción obtenga los bloqueos en la entidad primaria que toca mediante SELECCIONAR ... PARA ACTUALIZAR. O si toca varios, obtenga bloqueos en todos ellos pero en el mismo orden cada vez (el orden por clave principal es una buena opción).

10

Lo que PostgreSQL hace aquí está cubierto en la documentación en Explicit Locking. El ejemplo en la sección "Bloqueos" muestra lo que probablemente estás haciendo. La parte que quizás no haya esperado es que cuando ACTUALICE algo, adquiera un bloqueo en esa fila que continuará hasta que finalice la transacción. Si tienes varios clientes que hacen actualizaciones de más de una cosa a la vez, inevitablemente terminarás en un callejón sin salida a menos que salgas de tu camino para evitarlos.

Si tiene varias cosas que eliminan bloqueos implícitos como ACTUALIZAR, debe envolver toda la secuencia en bloques de transacción COMIENZO/COMPROMISO, y asegurarse de que es coherente con el orden en que adquieren bloqueos (incluso los implícitos como qué UPDATE agarra) en todas partes. Si necesita actualizar algo en la tabla A, entonces la tabla B, y una parte de la aplicación hace A, luego B, mientras que la otra hace B, luego A, se estanca un día. Dos ACTUALIZACIONES en contra de la misma tabla están destinadas a fallar de manera similar, a menos que pueda exigir algún orden de las dos que sea repetible entre los clientes. Clasificar por clave principal una vez que tiene el conjunto de registros para actualizar y siempre agarrando primero el "más bajo" es una estrategia común.

Es menos probable que sus INSERT sean culpables aquí, es mucho más difícil entrar en una situación de estancamiento, a menos que viole una clave principal como ya describieron las hormigas.

Lo que no quiere hacer es intentar duplicar el bloqueo en su aplicación, que se convertirá en un lío gigante de escalabilidad y confiabilidad (y probablemente aún resulte en puntos muertos de la base de datos). Si no puede evitar esto dentro de los límites de los métodos estándar de bloqueo de base de datos, considere usar el servicio de bloqueo de avisos o explícita LOCK TABLE para aplicar lo que necesita en su lugar. Eso le ahorrará un mundo de codificaciones dolorosas al tratar de empujar todas las cerraduras hacia el lado del cliente. Si tiene varias actualizaciones en una tabla y no puede hacer cumplir el orden en que ocurren, no tiene más remedio que bloquear toda la tabla mientras las ejecuta; esa es la única ruta que no introduce un potencial para un punto muerto.

Cuestiones relacionadas