【发布时间】:2020-03-11 19:44:30
【问题描述】:
我有一个带有 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,所以我们再次尝试 INSERT,并得到 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。
在阅读了上面的链接和网络上的其他资源之后,我真的不知道接下来该尝试什么才能可靠地工作,没有死锁和其他类似的可怕事情。
想法?帮助?谢谢!
【问题讨论】:
-
你没有锁定数据库,你期望什么?
-
这就是我问这个问题的原因:)...如何最好地锁定该行。
-
"...所以我们尝试再次插入,并得到重复输入错误。" ——没错,这就是它应该做的。你期待什么?
-
“id”不应该由
INSERT操作本身产生吗?为什么要在数据库之外生成 id?似乎是人造的。 -
似乎
select for update解决了这个问题,因为它声称也可以防止插入:dev.mysql.com/doc/refman/8.0/en/…。select for update是否在您的测试中以相同的 id 阻止select for update?
标签: mysql transactions dbi concurrentmodification