带有独占锁定的MySQL InnoDB死锁(FOR UPDATE)

时间:2011-03-25 12:24:40

标签: mysql transactions innodb deadlock

我这样做是为了确保只运行一次这个进程的实例(伪代码php / mysql innodb):

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的时间真的是毫秒 - 没有足够的时间来获得超时。如何使用此代码获得死锁?我不在此交易中使用其他表格。看起来死锁是不可能的。如果2个进程同时启动,则第一个获取该行锁定的进程将继续进行,另一个进程将等待锁定被释放。如果在1分钟内未释放锁定,则错误为“超时”,而不是死锁。

3 个答案:

答案 0 :(得分:7)

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上。

答案 1 :(得分:0)

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

如果可以避免,我通常不愿意使用交易;您可以通过

创建单个数据库查询来解决您的问题
insert into locks
select ('lockname', $pid)
from locks
where name not in
(select name from locks)

通过访问受影响的行,您可以查看该进程是否已在运行...

答案 2 :(得分: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
}