2016-11-15 163 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