2+ concurrent requests conflict during MySQL insert/update

yahermann :

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!

Khanh TO :

As specified here: https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html#innodb-record-locks

select for update should solve this problem as it claims to prevent inserting as well, the first request with select for update should block the second request select for update with the same id.

Another requirement is that the isolation level has to be at least read committed

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=291311&siteId=1