Reputation: 994
I have what is essentially a counter that users can increment.
However, I want to avoid the race condition of two users incrementing the counter at once.
Is there a way to atomically increment a counter using Gorm as opposed to fetching the value from the database, incrementing, and finally updating the database?
Upvotes: 3
Views: 7987
Reputation: 994
A possible solution is to use GORM transactions (https://gorm.io/docs/transactions.html).
err := db.Transaction(func(tx *gorm.DB) error {
// Get model if exist
var feature models.Feature
if err := tx.Where("id = ?", c.Param("id")).First(&feature).Error; err != nil {
return err
}
// Increment Counter
if err := tx.Model(&feature).Update("Counter", feature.Counter+1).Error; err != nil {
return err
}
return nil
})
if err != nil {
c.Status(http.StatusInternalServerError)
return
}
c.Status(http.StatusOK)
Upvotes: 2
Reputation: 1977
If you want to use the basic ORM features, you can use FOR UPDATE
as query option when retrieving the record, the database will lock the record for that specific connection until that connection issues an UPDATE
query to change that record.
Both the SELECT
and UPDATE
statements must happen on the same connection, which means you need to wrap them in a transaction (otherwise Go may send the second query over a different connection).
Please note that this will make every other connection that wants to SELECT
the same record wait until you've done the UPDATE
. That is not an issue for most applications, but if you either have very high concurrency or the time between SELECT ... FOR UPDATE
and the UPDATE
after that is long, this may not be for you.
In addition to FOR UPDATE
, the FOR SHARE
option sounds like it can also work for you, with less locking contentions (but I don't know it well enough to say this for sure).
Note: This assumes you use an RDBMS that supports SELECT ... FOR UPDATE
; if it doesn't, please update the question to tell us which RDBMS you are using.
Another option is to just go around the ORM and do db.Exec("UPDATE counter_table SET counter = counter + 1 WHERE id = ?", 42)
(though see https://stackoverflow.com/a/29945125/1073170 for some pitfalls).
Upvotes: 4