我有一个带有MySQL数据库的中型流量网站,当2个以上的并发请求尝试更新同一行时,偶尔会出现Duplicate entry错误。
我们使用Perl / DBI访问数据库。
Perl'ish伪代码:
$dbh->begin_work;
my $row = $dbh->selectrow_hashref( "SELECT * FROM mytable WHERE id=$some_id" );
if ( defined($row) ) {
# ... do stuff; uses $row ...
$dbh->do( "UPDATE mytable SET ... WHERE id=$some_id" );
}
else {
# ... do other stuff, different from above ...
$dbh->do( "INSERT INTO mytable SET id=$some_id, ... " );
sleep 30; # added for emphasis
}
$dbh->commit;
[id
列显然是unique
。
要重复/重述此问题,假设请求#1出现了。插入行。在睡眠期间,请求#2出现; $row
是undef的,因为我们尚未提交请求#1,因此我们尝试再次插入,并出现Duplicate entry错误。
我知道为什么会这样-因为我们没有锁定。这就是背景。问题是在这种背景下如何实现锁定。
[不幸的是,INSERT INTO ... ON DUPLICATE KEY UPDATE...
不起作用,因为根据$row
的存在,我们正在做略有不同的事情。
我按此处所述调查了SELECT ... FOR UPDATE
和SELECT ... LOCK IN SHARE MODE
:
MySQL InnoDB: Difference Between `FOR UPDATE` and `LOCK IN SHARE MODE`
但是因为我们在请求#1期间插入了新行,所以在插入之前没有要锁定的行会锁定请求#2。
在阅读了上面的链接和网络上的其他资源之后,我真的不知道接下来该怎么做才能可靠地工作,而不会出现死锁和其他类似的可怕事情。
想法?救命?谢谢!
此处指定:https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html#innodb-record-locks
[select for update
应该解决此问题,因为它声称也可以防止插入,带有select for update
的第一个请求应该阻止具有相同ID的第二个请求select for update
。
另一个要求是隔离级别必须至少是读已提交