2010-04-07 26 views
25

Tengo una tabla MySQL con aproximadamente 5,000,000 filas que constantemente se actualizan en pequeñas formas mediante procesos paralelos de Perl que se conectan a través de DBI. La tabla tiene alrededor de 10 columnas y varios índices.Error al trabajar con MySQL "Se ha encontrado un punto muerto al intentar obtener un bloqueo; intente reiniciar la transacción"

Una operación bastante común da lugar al error de seguimiento veces:

DBD::mysql::st execute failed: Deadlock found when trying to get lock; try restarting transaction at Db.pm line 276. 

La instrucción SQL que desencadena el error es algo como esto:

UPDATE file_table SET a_lock = 'process-1234' WHERE param1 = 'X' AND param2 = 'Y' AND param3 = 'Z' LIMIT 47 

El error se dispara sólo a veces. Calculo en 1% de llamadas o menos. Sin embargo, nunca sucedió con una tabla pequeña y se ha vuelto más común a medida que la base de datos ha crecido.

Tenga en cuenta que estoy usando el campo a_lock en file_table para asegurarme de que los cuatro procesos casi idénticos que estoy ejecutando no intenten trabajar en la misma fila. El límite está diseñado para dividir su trabajo en pequeños trozos.

No he hecho mucho ajuste en MySQL o DBD :: mysql. MySQL es una implementación estándar de Solaris, y la conexión de base de datos está configurado de la siguiente manera:

my $dsn = "DBI:mysql:database=" . $DbConfig::database . ";host=${DbConfig::hostname};port=${DbConfig::port}"; 
my $dbh = DBI->connect($dsn, $DbConfig::username, $DbConfig::password, { RaiseError => 1, AutoCommit => 1 }) or die $DBI::errstr; 

que he visto en línea que varias otras personas han informado de errores similares y que esto puede ser una situación de bloqueo genuino.

Tengo dos preguntas:

  1. ¿Qué es exactamente mi situación está causando el error anterior?

  2. ¿Hay una manera simple de evitarlo o disminuir su frecuencia? Por ejemplo, ¿cómo hago para "reiniciar la transacción en Db.pm línea 276"?

Gracias de antemano.

Respuesta

61

Si está utilizando InnoDB o cualquier nivel de fila RDBMS transaccionales, entonces es posible que cualquier transacción de escritura puede provocar un estancamiento, incluso en situaciones perfectamente normales. Las tablas más grandes, las escrituras más grandes y los bloques de transacción largos a menudo aumentan la probabilidad de que ocurran interbloqueos. En su situación, es probable que sea una combinación de estos.

La única manera de manejar realmente los bloqueos es escribir el código para esperarlos. Esto generalmente no es muy difícil si el código de su base de datos está bien escrito. A menudo puede simplemente poner un try/catch alrededor de la lógica de ejecución de la consulta y buscar un punto muerto cuando se producen errores. Si captura uno, lo normal es intentar ejecutar la consulta fallida nuevamente.

Le recomiendo que lea this page en el manual de MySQL. Tiene una lista de cosas que hacer para ayudar a hacer frente a los bloqueos y reducir su frecuencia.

+2

¿Cuáles son los códigos de error que necesitamos detectar entonces? ¿Capturar solo a 1205 es suficiente? Hay más de 900 códigos de error en http://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html. ¿Cómo sabe todos los códigos que debemos detectar para implementar una solución adecuada para su sugerencia de prueba/captura? – Pacerier

+0

¿Significa esto que aparte de 'InnoDB o cualquier RDBMS transaccional a nivel de fila' no tiene estos problemas? –

5

Tenga en cuenta que si usa SELECT FOR UPDATE para realizar una comprobación de unicidad antes de una inserción, obtendrá un punto muerto para cada condición de carrera a menos que active la opción innodb_locks_unsafe_for_binlog. Un método libre de interbloqueo para comprobar la singularidad consiste en insertar ciegamente una fila en una tabla con un índice único utilizando INSERT IGNORE, y luego verificar el recuento de filas afectadas.

añadir la siguiente línea para my.cnf archivo

innodb_locks_unsafe_for_binlog = 1

#

1 - EN
0 - OFF

#
+0

Esto resolvió todos mis problemas con el almacenamiento de asociaciones de ActiveRecord en entornos multiproceso. – lightyrs

+2

Habilitar 'innodb_locks_unsafe_for_binlog' puede causar problemas fantasma porque otras sesiones pueden insertar nuevas filas en los espacios cuando se desactiva el bloqueo de espacio. – shivam

9

La respuesta es correcta, sin embargo, la documentación de Perl sobre cómo manejar deadlocks es un poco escaso y tal vez confuso con PrintError, RaiseError y HandleError opciones. Parece que en lugar de ir con HandleError, utiliza Print and Raise y luego usa algo como Try: Tiny para ajustar tu código y comprobar si hay errores. El siguiente código proporciona un ejemplo en el que el código db se encuentra dentro de un ciclo while que volverá a ejecutar una instrucción sql con error cada 3 segundos. El bloque de captura recibe $ _, que es el mensaje de error específico. Paso esto a una función de controlador "dbi_err_handler" que comprueba $ _ contra una serie de errores y devuelve 1 si el código debe continuar (rompiendo el bucle) o 0 si es un punto muerto y debe volverse a intentar ...

$sth = $dbh->prepare($strsql); 
my $db_res=0; 
while($db_res==0) 
{ 
    $db_res=1; 
    try{$sth->execute($param1,$param2);} 
    catch 
    { 
     print "caught $_ in insertion to hd_item_upc for upc $upc\n"; 
     $db_res=dbi_err_handler($_); 
     if($db_res==0){sleep 3;} 
    } 
} 

dbi_err_handler debe tener por lo menos lo siguiente:

sub dbi_err_handler 
{ 
    my($message) = @_; 
    if($message=~ m/DBD::mysql::st execute failed: Deadlock found when trying to get lock; try restarting transaction/) 
    { 
     $caught=1; 
     $retval=0; # we'll check this value and sleep/re-execute if necessary 
    } 
    return $retval; 
} 

debe incluir otros errores que desea manejar y conjunto $ RETVAL dependiendo de si desea volver a ejecutar o continuar ..

Espero que esto ayude a alguien -

0

La idea de reintentar la consulta en caso de excepción de interbloqueo es buena, pero puede ser terriblemente lenta, ya que la consulta de mysql seguirá esperando que se desbloqueen los bloqueos. Y en caso de estancamiento, mysql está tratando de encontrar si hay un punto muerto, e incluso después de descubrir que hay un punto muerto, espera un momento antes de expulsar un hilo para salir de la situación de estancamiento.

Lo que hice cuando me enfrenté a esta situación es implementar el bloqueo en su propio código, ya que es el mecanismo de bloqueo de mysql está fallando debido a un error. Así que implementé mi propio bloqueo de nivel de fila en mi código java:

private HashMap<String, Object> rowIdToRowLockMap = new HashMap<String, Object>(); 
private final Object hashmapLock = new Object(); 
public void handleShortCode(Integer rowId) 
{ 
    Object lock = null; 
    synchronized(hashmapLock) 
    { 
     lock = rowIdToRowLockMap.get(rowId); 
     if (lock == null) 
     { 
      rowIdToRowLockMap.put(rowId, lock = new Object()); 
     } 
    } 
    synchronized (lock) 
    { 
     // Execute your queries on row by row id 
    } 
} 
+4

Desafortunadamente, es probable que la mayoría de los usuarios que se encuentren con este problema tengan que lidiar con varias máquinas o procesen datos volcados en una sola instancia de MySQL. El bloqueo de nivel de fila en la aplicación simplemente no es una opción para la mayoría de los usuarios. – dgtized

Cuestiones relacionadas