Pabi
Pabi

Reputation: 994

Go Gorm Atomic Update to Increment Counter

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

Answers (2)

Pabi
Pabi

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

Jory Geerts
Jory Geerts

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

Related Questions