2011-03-25 24 views
6

que hacer esto para asegurarse una sola vez instancia de este proceso está en ejecución (pseudo código php/InnoDB MySQL):MySQL InnoDB bloqueo muerto en SELECT con bloqueo exclusivo (FOR UPDATE)

START TRANSACTION 
$rpid = SELECT `value` FROM locks WHERE name = "lock_name" FOR UPDATE 
$pid = posix_getpid(); 
if($rpid > 0){ 
    $isRunning = posix_kill($rpid, 0); 
    if(!$isRunning){ // isRunning 
    INSERT INTO locks values('lock_name', $pid) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`) 
    }else{ 
    ROLLBACK 
    echo "Allready running...\n"; 
    exit(); 
    } 
}else{ // if rpid == 0 - 
    INSERT INTO locks values('lock_name', $pid) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`) 
} 
COMMIT 

............... 

//free the pid 
INSERT INTO locks values('lock_name', 0) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`) 

bloqueos de tabla contienen estos campos :

id - primary, autoinc 
name - varchar(64) unique key 
description - text 
value - text 

creo que el tiempo desde el inicio TRANSACTIN a COMMIT/ROLLBACK es realmente milisegundos - no hay tiempo suficiente para conseguir incluso tiempo de espera. ¿Cómo es posible tener un punto muerto con este código? No uso otras tablas dentro de esta transacción. Parece que el punto muerto no es posible. Si 2 procesos comienzan al mismo tiempo, el primero que obtiene el bloqueo en esa fila procederá y el otro esperará a que se libere el bloqueo. Si el bloqueo no se libera en 1 minuto, el error es "tiempo de espera", no interbloqueo.

Respuesta

0

Justo lo descubrió gracias a Quassnoi 's respuesta ...

que puedo hacer:

$myPid = posix_getpid(); 
$gotIt = false; 
while(true){ 
    START TRANSACTION; 
    $pid = SELECT ... FOR UPDATE; // read pid and get lock on it 
    if(mysql_num_rows($result) == 0){ 
    ROLLBACK;// release lock to avoid deadlock 
    INSERT IGNORE INTO locks VALUES('lockname', $myPid); 
    }else{ 
    //pid existed, no insert is needed 
    break; 
    } 
} 

if($pid != $myPid){ //we did not insert that 
    if($pid>0 && isRunning($pid)){ 
    ROLLBACK; 
    echo 'another process is running'; 
    exit; 
    }{ 
    // no other process is running - write $myPid in db 
    UPDATE locks SET value = $myPid WHERE name = 'lockname'; // update is safe 
    COMMIT; 
    } 
}else{ 
    ROLLBACK; // release lock 
} 
6

SELECT FOR UPDATE obtiene un bloqueo exclusivo intencionado en la tabla antes de obtener el bloqueo exclusivo en el registro.

Por lo tanto, en este escenario:

X1: SELECT FOR UPDATE -- holds IX, holds X on 'lock_name' 
X2: SELECT FOR UPDATE -- holds IX, waits for X on 'lock_name' 
X1: INSERT -- holds IX, waits for X for the gap on `id` 

se produce un punto muerto, ya que ambas operaciones son la celebración de una cerradura IX sobre la mesa y la espera de una cerradura X en los registros.

En realidad, este mismo escenario se describe en el MySQL manual on locking.

Para solucionar esto, debe deshacerse de todos los índices excepto el que está buscando, es decir lock_name.

Simplemente suelte la tecla principal en id.

+0

Así que si uso ACTUALIZACIÓN en lugar de INSERT, clave primaria no le hará daño. Además, el problema no está en una mesa. Hay otras tablas que no puedo eliminar la clave principal. ¿Hay alguna otra solución? Debería haber una forma de guardar insertando una fila cuando hace X bloquear en una fila – NickSoft

+0

Supongo que lo difícil es tener bloqueos de tabla con solo un índice único y usarlo para bloquear antes del bloqueo X e insertarlo en tablas más complejas. Pero eso tiene que hacerse antes de cada inserción que pueda estancar y espero que haya otra forma. Entonces, ¿hay alguna otra forma de insertar de forma segura en tablas con más de un índice (único?). – NickSoft

0

Sin ver el código PHP real, es difícil estar seguro, pero ¿es posible que no esté usando la misma conexión de base de datos entre ejecutar SELECT e INSERT?

Normalmente prefiero no usar transacciones si puedo evitarlo; el problema podría resolverse mediante la creación de una única base de datos de consulta a lo largo de las líneas de

insert into locks 
select ('lockname', $pid) 
from locks 
where name not in 
(select name from locks) 

Al acceder a las filas afectadas, se puede ver si el proceso ya está en marcha ...

+0

No hay forma de comprobar si el pid seleccionado en la tabla aún se está ejecutando antes de realizar la actualización. ¿Qué pasa si 'lockname' ya está en la tabla, pero se ha bloqueado (no se está ejecutando)? El nuevo proceso nunca llevará el bloqueo. Necesito seleccionar & lcok-> comprobar si se está ejecutando-> si no está ejecutando la actualización con el nuevo pid – NickSoft

Cuestiones relacionadas