2011-03-25 96 views
6

我这样做是为了确保只有一次这个过程的实例运行(伪代码的PHP/MySQL的InnoDB):MySQL的InnoDB的死锁与排它锁(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`) 

表锁包含这些字段:

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

我相信从START TRANSACTIN到COMMIT/ROLLBACK时间真的毫秒 - 没有足够的时间,甚至得到超时。这个代码怎么可能发生死锁?我不在这个交易中使用其他表。看起来死锁是不可能的。如果两个进程同时启动,第一个获得该行锁定的进程将继续,另一个将等待锁定释放。如果锁在1分钟内未释放,则错误为“超时”,而非死锁。

回答

0

就想通了感谢Quassnoi的答案...

我可以这样做:

$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在获得对记录的排他锁之前,获取表上的意向排他锁。

因此,在这种情况下:

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` 

死锁发生时,由于交易双方都拿着桌子上的IX锁和等待X锁的记录。

实际上,这种情况在MySQL manual on locking中有描述。

要解决此问题,您需要清除除您正在搜索的索引之外的所有索引,即lock_name

只需将主键放在id上即可。

+0

所以,如果我使用UPDATE而不是INSERT,主键就不会受到伤害。此外,问题不在一张桌子上。还有其他表格我无法删除主键。还有其他解决方法吗?当你在一行上进行X锁定时,应该有一种方法来保存地插入一行 – NickSoft 2011-03-28 08:39:22

+0

我想困难的方法是让表锁只有一个唯一索引,并在X锁定和插入更复杂的表之前使用它来锁定。但是这必须在每一个可能发生死锁的插入之前完成,我希望有其他方法。那么是否有另一种方法可以在具有多个(唯一?)索引的表上安全插入。 – NickSoft 2011-03-28 08:44:02

0

在没有看到实际的PHP代码的情况下,很难确定 - 但是在运行SELECT和INSERT之间可能没有实际使用相同的数据库连接?

我通常不喜欢使用交易,如果我可以避免它;你的问题可以通过建立沿

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

行一个单独的数据库查询通过访问受影响的行来解决,你可以看到,如果进程已经运行...

+0

在执行更新之前,无法检查从表中选择的pid是否仍在运行。如果'lockname'已经在表中,但它已经崩溃(没有运行)?新进程永远不会取得锁定。我需要选择&lcok->检查它是否正在运行 - >如果没有使用新的pid运行更新 – NickSoft 2011-03-28 08:30:12