2011-03-25 53 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