I have a medium-traffic website with a MySQL database, and I'm seeing occasional Duplicate entry errors when 2+ concurrent requests try to update the same row.
We use Perl/DBI to access the database.
Perl'ish pseudo-code:
$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;
The id
column is unique
, obviously.
To repeat/rephrase the issue, suppose request #1 comes along. Row is inserted. While SLEEPing, request #2 comes along; $row
is undef because we still haven't committed request #1, so we try to INSERT again, and get the Duplicate entry error.
I understand why this is happening -- because we're not locking. This is the background. The question is how to implement a lock given this background.
Unfortunately, INSERT INTO ... ON DUPLICATE KEY UPDATE...
doesn't work since we're doing slightly different things depending on existence of $row
.
I looked into SELECT ... FOR UPDATE
and SELECT ... LOCK IN SHARE MODE
as described here:
MySQL InnoDB: Difference Between `FOR UPDATE` and `LOCK IN SHARE MODE`
but because we're inserting a new row during request #1, there is no row to lock prior to the insert, that would lock out request #2.
After reading the above link and other resources on the web, I don't really know what to try next that would work reliably, without deadlocks and other scary things like that.
Ideas? Help? Thanks!