Tengo una tabla innoDB que registra a los usuarios en línea. El usuario lo actualiza en cada actualización de la página para realizar un seguimiento de las páginas en las que se encuentra y su última fecha de acceso al sitio. Luego tengo un cron que se ejecuta cada 15 minutos para ELIMINAR registros antiguos.
Obtuve un 'Deadlock encontrado cuando trataba de obtener el bloqueo; intente reiniciar la transacción 'durante unos 5 minutos anoche y parece ser cuando ejecuta INSERT en esta tabla. ¿Alguien puede sugerir cómo evitar este error?
=== EDITAR ===
Estas son las consultas que se están ejecutando:
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 página de actualización:
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
Luego hace algunos recuentos para registrar algunas estadísticas (es decir: miembros en línea, visitantes en línea).
Respuestas:
Un truco fácil que puede ayudar con la mayoría de los puntos muertos es ordenar las operaciones en un orden específico.
Obtiene un punto muerto cuando dos transacciones intentan bloquear dos bloqueos en órdenes opuestas, es decir:
Si ambas se ejecutan al mismo tiempo, la conexión 1 bloqueará la llave (1), la conexión 2 bloqueará la llave (2) y cada conexión esperará a que la otra libere la llave -> punto muerto.
Ahora, si cambió sus consultas de modo que las conexiones bloquearían las claves en el mismo orden, es decir:
Será imposible llegar a un punto muerto.
Entonces esto es lo que sugiero:
Asegúrese de no tener otras consultas que bloqueen el acceso a más de una clave a la vez, excepto la declaración de eliminación. si lo hace (y sospecho que lo hace), ordene su DÓNDE en (k1, k2, .. kn) en orden ascendente.
Arregle su declaración de eliminación para que funcione en orden ascendente:
Cambio
A
Otra cosa a tener en cuenta es que la documentación de mysql sugiere que en caso de un punto muerto, el cliente debería volver a intentarlo automáticamente. Puede agregar esta lógica a su código de cliente. (Digamos, 3 reintentos en este error en particular antes de darse por vencido).
fuente
El punto muerto ocurre cuando dos transacciones se esperan para adquirir un bloqueo. Ejemplo:
Existen numerosas preguntas y respuestas sobre puntos muertos. Cada vez que inserta / actualiza / o elimina una fila, se adquiere un bloqueo. Para evitar un punto muerto, debe asegurarse de que las transacciones simultáneas no actualicen la fila en un orden que podría resultar en un punto muerto. En términos generales, intente adquirir el bloqueo siempre en el mismo orden, incluso en diferentes transacciones (por ejemplo, siempre la tabla A primero, luego la tabla B).
Otra razón para el punto muerto en la base de datos puede ser la falta de índices . Cuando se inserta / actualiza / elimina una fila, la base de datos debe 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 posible que se obtenga otro bloqueo que no sea la fila que se modifica. Asegúrese de tener siempre un índice en las claves externas (y, por supuesto, las claves primarias), de lo contrario podría resultar en un bloqueo de tabla en lugar de un bloqueo de fila . Si se produce el bloqueo de la tabla, la contención del bloqueo es mayor y aumenta la probabilidad de un punto muerto.
fuente
Es probable que la instrucción delete afecte a una gran fracción del total de filas en la tabla. Eventualmente, esto podría llevar a que se adquiera un bloqueo de tabla al eliminar. Mantener un bloqueo (en este caso bloqueos de fila o página) y adquirir más bloqueos siempre es un riesgo de punto muerto. Sin embargo, no puedo explicar por qué la declaración de inserción conduce a una escalada de bloqueo: podría tener que ver con la división / adición de páginas, pero alguien que conozca MySQL mejor tendrá que completarla.
Para empezar, puede valer la pena tratar de adquirir explícitamente un bloqueo de tabla de inmediato para la instrucción delete. Ver LOCK TABLES y Table bloqueo de problemas .
fuente
Puede intentar hacer
delete
funcionar ese trabajo insertando primero la clave de cada fila que se eliminará en una tabla temporal como este pseudocódigoRomperlo así es menos eficiente pero evita la necesidad de mantener un bloqueo de rango de teclas durante el
delete
.Además, modifique sus
select
consultas para agregar unawhere
cláusula que excluya las filas anteriores a 900 segundos. Esto evita la dependencia del trabajo cron y le permite reprogramar que se ejecute con menos frecuencia.Teoría sobre los puntos muertos: no tengo muchos antecedentes en MySQL, pero aquí va ...
delete
Mantendrá un bloqueo de rango de teclas para fecha y hora, para evitarwhere
que se agreguen filas que coincidan con su cláusula en el medio de la transacción y, a medida que encuentre filas para eliminar, intentará adquirir un bloqueo en cada página que está modificando. Elinsert
va a adquirir un bloqueo en la página en la que se está insertando, y luego intentará adquirir el bloqueo de teclas. Normalmenteinsert
, esperará pacientemente a que se abra ese bloqueo de teclas, pero esto sedelete
bloqueará si intenta bloquear la misma página queinsert
está utilizando porquedelete
necesita ese bloqueo de página yinsert
necesita ese bloqueo de tecla. Sin embargo, esto no parece adecuado para los insertos, eldelete
yinsert
están utilizando 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
fuente
En caso de que alguien todavía tenga problemas con este problema:
Me enfrenté a un problema similar en el que 2 solicitudes llegaban al servidor al mismo tiempo. No hubo ninguna situación como la siguiente:
Entonces, me sorprendió por qué está ocurriendo un punto muerto.
Luego descubrí que había una relación padre-hijo entre 2 tablas debido a la clave externa. Cuando estaba insertando un registro en la tabla secundaria, la transacción estaba adquiriendo un bloqueo en la fila de la tabla primaria. Inmediatamente después de eso, estaba tratando de actualizar la fila principal que estaba activando la elevación del bloqueo a EXCLUSIVO. Como la segunda transacción concurrente ya tenía un bloqueo COMPARTIDO, estaba causando un punto muerto.
Consulte: https://blog.tekenlight.com/2019/02/21/database-deadlock-mysql.html
fuente
Para los programadores de Java que usan Spring, he evitado este problema usando un aspecto AOP que reintenta automáticamente las transacciones que se encuentran en puntos muertos transitorios.
Consulte @RetryTransaction Javadoc para obtener más información.
fuente
Tengo un método, cuyas partes internas están envueltas en una MySqlTransaction.
El problema del punto muerto se me apareció cuando ejecuté el mismo método en paralelo consigo mismo.
No hubo un problema al ejecutar una sola instancia del método.
Cuando eliminé MySqlTransaction, pude ejecutar el método en paralelo consigo mismo sin problemas.
Solo compartiendo mi experiencia, no estoy abogando por nada.
fuente
cron
es peligroso. Si una instancia de cron no termina antes de la próxima, es probable que luchen entre sí.Sería mejor tener un trabajo en ejecución continua que eliminaría algunas filas, suspendería algunas y luego repetiría.
Además,
INDEX(datetime)
es muy importante para evitar puntos muertos.Pero, si la prueba de fecha y hora incluye más de, digamos, el 20% de la tabla,
DELETE
realizará un escaneo de la tabla. Los trozos más pequeños eliminados con más frecuencia son una solución alternativa.Otra razón para ir con trozos más pequeños es bloquear menos filas.
Línea de fondo:
INDEX(datetime)
Otras técnicas de eliminación: http://mysql.rjweb.org/doc.php/deletebig
fuente