MySQL死锁,带有一个引发触发器的插件

时间:2016-11-15 20:09:54

标签: mysql database innodb deadlock

我发现了一些罕见的死锁错误。我理解当两个查询作业取决于彼此的结果时发生死锁,因此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

我在likes表上有一个Trigger,定义如下:

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)

这不应该是锁定等待而不是死锁?

如何在不重启交易的情况下解决此问题?

2 个答案:

答案 0 :(得分:3)

线程2在users表的行中保存共享锁。

然后,线程1尝试在同一行上获得独占锁,然后进入锁定等待。

但是线程1没有机会超时,因为线程2然后试图将他的锁升级为独占...但为了做到这一点,他必须等待线程1,这是锁定等待,但它正在等待第2个帖子。

他们各自阻挡另一个。

这是一个僵局。

服务器选择要杀死的事务,这样他们就不会不必要地阻塞对方。

死锁检测允许一个线程立即成功而牺牲另一个线程。否则他们都会被锁定等待,直到其中一人因等待太久而死亡。

您处于自动提交模式,但当然,这并不意味着您不在交易中。使用InnoDB的每个查询仍然在事务中处理,但是使用自动提交时,在查询开始执行时隐式启动事务,并在成功时隐式提交事务。

答案 1 :(得分: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似乎是多余的存储。