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

Nic*_*oft 6 mysql deadlock innodb transactions

我这样做是为了确保只运行一次这个进程的实例(伪代码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`)
Run Code Online (Sandbox Code Playgroud)

表锁包含以下字段:

id - primary, autoinc
name - varchar(64) unique key
description - text
value - text
Run Code Online (Sandbox Code Playgroud)

我相信从START TRANSACTIN到COMMIT/ROLLBACK的时间真的是毫秒 - 没有足够的时间来获得超时.如何使用此代码获得死锁?我不在此交易中使用其他表格.看起来死锁是不可能的.如果2个进程同时启动,则第一个获取该行锁定的进程将继续进行,另一个进程将等待锁定被释放.如果锁定在1分钟内未释放,则错误为"超时",而不是死锁.

Qua*_*noi 8

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`
Run Code Online (Sandbox Code Playgroud)

发生死锁,因为两个事务都IX在对表进行X锁定并等待对记录的锁定.

实际上,这种情况MySQL在锁定手册中有所描述.

要解决这个问题,你需要除掉你正在搜索的索引之外的所有索引lock_name.

只需放下主键即可id.


Nic*_*oft 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
}
Run Code Online (Sandbox Code Playgroud)