我这样做是为了确保只运行一次这个进程的实例(伪代码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分钟内未释放锁定,则错误为“超时”,而不是死锁。
答案 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
}