2016-11-15 165 views
0

我發現了一些罕見的死鎖錯誤發生。我知道當兩個查詢工作取決於對方的結果時會發生死鎖,所以MySQL會回滾其中一個。但在我的情況下,MySQL處於自動提交模式,我插入一條觸發觸發器的新記錄。所以我不明白它會導致死鎖的情況。MySQL插入死鎖,引發觸發器

這裏是我的表的模式:

----用戶表----

CREATE TABLE `users` (
`insta_id` bigint(20) unsigned NOT NULL, 
`name` varchar(50) NOT NULL, 
`password` varchar(60) NOT NULL, 
`gem` int(10) unsigned DEFAULT '20', 
`coin` int(10) unsigned DEFAULT '20', 
PRIMARY KEY (`insta_id`), 
UNIQUE KEY `insta_id` (`insta_id`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1 

--- like_requests表---

CREATE TABLE `like_requests` (
`req_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 
`insta_id` bigint(20) unsigned NOT NULL, 
`media_id` varchar(50) NOT NULL, 
`remaining_like` int(10) unsigned NOT NULL, 
`active` tinyint(1) NOT NULL DEFAULT '1', 
`count` int(10) unsigned NOT NULL, 
PRIMARY KEY (`req_id`), 
KEY `insta_id` (`insta_id`), 
KEY `media_id` (`media_id`), 
CONSTRAINT `like_requests_ibfk_1` FOREIGN KEY (`insta_id`) REFERENCES `users`(`insta_id`) 
) ENGINE=InnoDB AUTO_INCREMENT=103902 DEFAULT CHARSET=latin1 

---喜歡錶---

CREATE TABLE `likes` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 
`insta_id` bigint(20) unsigned NOT NULL, 
`media_id` varchar(50) NOT NULL, 
`req_id` bigint(20) unsigned DEFAULT NULL, 
`date` timestamp NULL DEFAULT CURRENT_TIMESTAMP, 
PRIMARY KEY (`id`), 
UNIQUE KEY `id` (`id`), 
KEY `req_id` (`req_id`), 
KEY `insta_id` (`insta_id`), 
KEY `media_id` (`media_id`), 
CONSTRAINT `likes_ibfk_1` FOREIGN KEY (`req_id`) REFERENCES `like_requests`(`req_id`), 
CONSTRAINT `likes_ibfk_2` FOREIGN KEY (`insta_id`) REFERENCES `users`(`insta_id`) 
) ENGINE=InnoDB AUTO_INCREMENT=1704209 DEFAULT CHARSET=latin1 

我有一個觸發器o ñ喜歡錶,定義如下:

CREATE TRIGGER `after_insert_likes` AFTER INSERT ON `likes` 
    FOR EACH ROW BEGIN 
     UPDATE users SET users.coin=users.coin+1 
      WHERE users.insta_id = NEW.insta_id LIMIT 1; 
     IF NEW.req_id IS NOT NULL THEN 
      UPDATE like_requests 
       SET like_requests.remaining_like = like_requests.remaining_like-1 
       WHERE like_requests.req_id = NEW.req_id 
        AND like_requests.remaining_like > 0 
       LIMIT 1; 
     END IF; 
    END 

隨着做一些簡單的插入:

$sql = "INSERT INTO likes (insta_id,media_id,req_id) VALUES (?,?,?);"; 
    $pdo = $this->db; 

    $statement = $pdo->prepare($sql); 
    $statement->bindValue(1,$data['id'],PDO::PARAM_INT); 
    $statement->bindValue(2,$data['media_id']); 
    $statement->bindValue(3,$data['req_id'],PDO::PARAM_INT); 

    try 
    { 
     $statement->execute(); 
     return GetOkResponseWithMessage($response,"Like was submitted"); 
    } 
    catch (PDOException $exc) 
    { 
     return GetErrorResponseWithMessage($response,$exc->getMessage(),500); 
    } 

我得到以下死鎖錯誤日誌:

*** (1) TRANSACTION: 
TRANSACTION 29031910, ACTIVE 1 sec starting index read 
mysql tables in use 4, locked 4 
LOCK WAIT 7 lock struct(s), heap size 1184, 3 row lock(s), undo log entries 1 
MySQL thread id 264238, OS thread handle 0x7f6522c6eb00, query id 753506  localhost xxxx updating 
UPDATE users SET users.coin=users.coin+1 WHERE users.insta_id=NEW.insta_id LIMIT 1 
*** (1) WAITING FOR THIS LOCK TO BE GRANTED: 
RECORD LOCKS space id 14 page no 1560 n bits 128 index `PRIMARY` of table `insta_star`.`users` trx table locks 4 total table locks 4 trx id 29031910 lock_mode X locks rec but not gap waiting lock hold time 0 wait time before grant 0 
*** (2) TRANSACTION: 
TRANSACTION 29031909, ACTIVE 1 sec starting index read 
mysql tables in use 4, locked 4 
7 lock struct(s), heap size 1184, 3 row lock(s), undo log entries 1 
MySQL thread id 264237, OS thread handle 0x7f65209f8b00, query id 753507 localhost xxxx updating 
UPDATE users SET users.coin=users.coin+1 WHERE users.insta_id=NEW.insta_id LIMIT 1 
*** (2) HOLDS THE LOCK(S): 
RECORD LOCKS space id 14 page no 1560 n bits 128 index `PRIMARY` of table `insta_star`.`users` trx table locks 4 total table locks 4 trx id 29031909 lock mode S locks rec but not gap lock hold time 0 wait time before grant 0 
*** (2) WAITING FOR THIS LOCK TO BE GRANTED: 
RECORD LOCKS space id 14 page no 1560 n bits 128 index `PRIMARY` of table `insta_star`.`users` trx table locks 4 total table locks 4 trx id 29031909 lock_mode X locks rec but not gap waiting lock hold time 0 wait time before grant 0 
*** WE ROLL BACK TRANSACTION (2) 

不應該爲此起來在鎖等待,而不是死鎖?

如何在不重新啓動事務的情況下解決此問題?

+0

你能添加觸發這個的插入嗎?如果你已經將它包裝在一個事務中,請發佈完整的博客 – e4c5

+0

@ e4c5插入代碼僅作爲單個查詢執行。我已經添加了代碼在PHP中插入 – BlackBrain

回答

1

likes,是(insta_id, media_id)是否唯一?或者也許(insta_id, req_id) ??或者也許所有3?如果是這樣,使它PRIMARY KEY並擺脫id all together. If you must keep ID , get rid of UNIQUE(ID), since PRIMARY KEY(ID)`提供該功能。

同樣,擺脫UNIQUE(insta_id)

的思考autocommitTRIGGER結合是一個幾個命令組成的交易:

BEGIN; 
INSERT INTO likes... -- Includes 2 uniqueness checks, 1 FK check 
UPDATE users ... 
if... UPDATE like_requests ... 
COMMIT; 

我的建議指標的變化可以加快速度了一些,從而減少死鎖的機會。更改可能甚至把僵局變成一個等待,但我懷疑它。

對死鎖的最佳防禦是與他們一起生活,並抓住他們並重放交易(在本例中爲INSERT)。

(不相關:) media_id似乎被冗餘存儲。

2

線程2在用戶表中的行上保存共享鎖。

然後,線程1嘗試獲取同一行上的排它鎖,並進入鎖等待狀態。

但是線程1不會有超時的機會,因爲線程2然後試圖將他的鎖升級到獨佔...但要做到這一點,他必須等待線程1,這是鎖定等待,但它正在等待線程2.

它們各自阻塞另一個。

這是一個僵局。

服務器選擇要殺死的事務,以便它們不會不必要地彼此阻塞。

死鎖檢測允許一個線程立即以犧牲另一個線程爲代價獲得成功。否則,他們都會陷入困境,等到其中一人因等待時間過長而死亡。


您處於自動提交模式,但當然,這並不意味着您不在交易中。每個使用InnoDB的查詢仍然在事務中處理,但是使用自動提交時,事務將在查詢開始執行時隱式啓動,並在成功執行時隱式提交。

+0

你有任何想法如何解決這個問題,而無需重新啓動交易? – BlackBrain