2010-02-25 20 views
192

Tengo una tabla innoDB que registra usuarios en línea. Se actualiza en cada actualización de página por un usuario para realizar un seguimiento de las páginas en las que se encuentran y su última fecha de acceso al sitio. Luego tengo un cron que se ejecuta cada 15 minutos para ELIMINAR registros antiguos.Cómo evitar mysql 'Deadlock encontrado al intentar obtener el bloqueo; intente reiniciar la transacción '

Recibí un 'Interbloqueo encontrado al intentar obtener el bloqueo; intente reiniciar la transacción 'durante aproximadamente 5 minutos anoche y parece ser cuando se ejecuta INSERT en esta tabla. ¿Puede alguien sugerir cómo evitar este error?

=== === EDITAR

Estas son las consultas que se ejecutan:

primera visita al sitio:

INSERT INTO onlineusers SET 
ip = 123.456.789.123, 
datetime = now(), 
userid = 321, 
page = '/thispage', 
area = 'thisarea', 
type = 3 

En cada actualización de la página:

UPDATE onlineusers SET 
ips = 123.456.789.123, 
datetime = now(), 
userid = 321, 
page = '/thispage', 
area = 'thisarea', 
type = 3 
WHERE id = 888 

Cron cada 15 minutos:

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND 

A continuación, hace algunos cálculos para registrar algunos datos (por ejemplo: miembros en línea, los visitantes en línea).

+0

¿Puede proporcionar más detalles sobre la estructura de la mesa? ¿Hay algún índice agrupado o no agrupado? –

+9

http://dev.mysql.com/doc/refman/5.1/en/innodb-deadlocks.html - Ejecutar "show engine innodb status" proporcionaría diagnósticos útiles. – Martin

Respuesta

205

Un truco fácil que puede ayudar con la mayoría de los bloqueos es ordenar las operaciones en un orden específico.

Se obtiene un punto muerto cuando dos transacciones están tratando de bloquear dos esclusas de órdenes opuestas, es decir:

  • de conexión 1: cerraduras con llave (1), cerraduras con llave (2);
  • conexión 2: bloquea la tecla (2), bloquea la tecla (1);

Si ambos funcionan al mismo tiempo, la conexión 1 bloqueará la tecla (1), la conexión 2 bloqueará la tecla (2) y cada conexión esperará a que la otra libere la tecla -> punto muerto.

Ahora, si ha cambiado sus consultas de manera que las conexiones podrían bloquear las teclas en el mismo orden, es decir:

  • conexión 1: cerraduras con llave (1), cerraduras de llave (2);
  • conexión 2: tecla de bloqueos(), tecla de bloqueos();

será imposible encontrar un punto muerto.

Así que esto es lo que sugieren:

  1. Asegúrese de que usted no tiene ningún tipo de duda que bloquean el acceso más de una tecla a la vez con excepción de la instrucción de eliminación. si lo haces (y sospecho que lo haces), ordena su DONDE en (k1, k2, .. kn) en orden ascendente.

  2. Fije su instrucción de eliminación para trabajar en orden ascendente:

Cambio

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND 

Para

DELETE FROM onlineusers WHERE id IN (SELECT id FROM onlineusers 
    WHERE datetime <= now() - INTERVAL 900 SECOND order by id) u; 

Otra cosa a tener en cuenta es que la documentación de MySQL sugieren que en caso de un punto muerto, el cliente debe volver a intentarlo automáticamente. puede agregar esta lógica a su código de cliente. (Digamos, 3 intentos en este error en particular antes de darse por vencido).

+1

Si tengo Transaction (autocommit = false), se lanza la excepción de interbloqueo. ¿Es suficiente simplemente reintentar el mismo statement.executeUpdate() o toda la transacción ahora está trucada y debería ser retrotraída + volver a ejecutar todo lo que se estaba ejecutando en ella? – Whome

+2

si tiene transacciones habilitadas, es todo o nada. si tuvo una excepción de cualquier tipo, está garantizado que toda la transacción no tuvo efecto. en ese caso, debería reiniciarlo todo. –

+1

Una eliminación basada en una selección en una tabla enorme es más lenta que una simple eliminación – Thermech

49

El bloqueo se produce cuando dos transacciones esperan entre sí para adquirir un bloqueo. Ejemplo:

  • Tx 1: Bloqueo de A, entonces B
  • Tx 2: Bloqueo B, entonces A

Hay numerosas preguntas y respuestas sobre los puntos muertos. Cada vez que inserta/actualiza/o elimina una fila, se adquiere un bloqueo. Para evitar el interbloqueo, debe asegurarse de que las transacciones simultáneas no actualicen la fila en un orden que podría provocar un interbloqueo. En general, intenta adquirir el bloqueo siempre en el mismo orden incluso en diferentes transacciones (por ejemplo, siempre la tabla A primero, luego la tabla B).

Otro motivo de interbloqueo en la base de datos puede ser índices faltantes. Cuando se inserta una fila/actualizar/eliminar, la base de datos necesita verificar las restricciones relacionales, es decir, asegurarse de que las relaciones sean consistentes. Para hacerlo, la base de datos debe verificar las claves externas en las tablas relacionadas. Es podría resultar en la adquisición de otro bloqueo que la fila que se modifica. Asegúrese de tener siempre el índice en las claves externas (y, por supuesto, las principales), de lo contrario podría resultar en un bloqueo de tabla en lugar de un bloqueo de fila. Si se produce un bloqueo de tabla, la contención de bloqueo es mayor y aumenta la probabilidad de interbloqueo.

+3

Así que tal vez mi problema es que el usuario ha actualizado la página y, por lo tanto, desencadenando una ACTUALIZACIÓN de un registro al mismo tiempo que el cron intenta ejecutar un DELETE en el registro. Sin embargo, estoy obteniendo el error en INSERTOS, por lo que el cron no eliminará los registros que acaban de crearse. Entonces, ¿cómo puede ocurrir un punto muerto en un registro que aún no se ha insertado? – David

+0

¿Puede proporcionarnos un poco más de información sobre la (s) tabla (s) y qué hacen exactamente las transacciones? – ewernli

+0

He editado la publicación inicial con información de consulta. – David

4

Es probable que la instrucción delete afecte a una gran fracción de las filas totales de la tabla. Eventualmente esto puede llevar a que se adquiera un bloqueo de tabla al eliminar. Aferrarse a un bloqueo (en este caso, bloqueos de fila o página) y adquirir más bloqueos siempre es un riesgo de interbloqueo. Sin embargo, no puedo explicar por qué la declaración de inserción lleva a una escalada de bloqueo: podría tener que ver con la división/adición de página, pero alguien que conozca Mysql mejor tendrá que completar allí.

Para empezar, puede valer la pena tratar de obtener explícitamente un bloqueo de tabla de inmediato para la instrucción delete. Ver LOCK TABLES y Table locking issues.

4

Usted puede tratar de tener que delete trabajo operan insertando primero la tecla de cada fila que desea borrar en una tabla temporal como este pseudocódigo

create temporary table deletetemp (userid int); 

insert into deletetemp (userid) 
    select userid from onlineusers where datetime <= now - interval 900 second; 

delete from onlineusers where userid in (select userid from deletetemp); 

Rompiendo que de esta manera es menos eficiente pero evita la necesidad para mantener un bloqueo de rango de clave durante delete.

Además, modifique sus consultas select para agregar una cláusula where, excluidas las filas anteriores a 900 segundos. Esto evita la dependencia del trabajo cron y le permite reprogramarlo para que se ejecute con menos frecuencia.

Teoría acerca de los interbloqueos: no tengo muchos antecedentes en MySQL, pero aquí va ... El delete va a mantener un bloqueo de rango de clave para fecha y hora, para evitar que se agreguen filas que coincidan con su cláusula where en el medio de la transacción, y como encuentra filas para eliminar, intentará adquirir un bloqueo en cada página que está modificando. El insert va a adquirir un bloqueo en la página en la que se está insertando, y luego intenta adquirir el bloqueo de tecla. Normalmente, el insert esperará pacientemente a que se abra ese bloqueo de teclas, pero esto bloqueará si el delete intenta bloquear la misma página que el insert porque el delete necesita ese bloqueo de página y el insert necesita ese bloqueo. Sin embargo, esto no parece correcto para las inserciones, el delete y el insert están usando rangos de fecha y hora que no se superponen, por lo que tal vez esté sucediendo algo más.

http://dev.mysql.com/doc/refman/5.1/en/innodb-next-key-locking.html

2

Para los programadores de Java utilizando la primavera, he evitado este problema usando un aspecto de AOP que vuelve a intentar automáticamente las transacciones que se ejecutan en los puntos muertos transitorios.

Ver @RetryTransaction Javadoc para obtener más información.

+0

¿Puedes actualizar tu enlace? está muerto –

+0

Actualizado - ¡gracias! – Archie

Cuestiones relacionadas