Gorm原子更新增量计数器

Go Gorm Atomic Update to Increment Counter

我有一个本质上是用户可以递增的计数器。

但是,我想避免两个用户同时递增计数器的竞争条件。

有没有一种方法可以使用 Gorm 以原子方式递增计数器,而不是从数据库中获取值、递增并最终更新数据库?

如果您想使用基本的 ORM 功能,您可以在检索记录时使用 FOR UPDATE 作为 query option,数据库将锁定该特定连接的记录,直到该连接发出 UPDATE查询以更改该记录。

SELECTUPDATE 语句 必须 发生在同一个连接上,这意味着你需要将它们包装在一个事务中(否则 Go 可能通过不同的连接发送第二个查询。

请注意,此 将使所有其他想要 SELECT 相同记录的连接等待 ,直到您完成 UPDATE。对于大多数应用程序来说这不是问题,但是如果您有非常高的并发性或者 SELECT ... FOR UPDATE 和之后的 UPDATE 之间的时间很长,这可能不适合您。

除了 FOR UPDATE 之外,FOR SHARE 选项听起来也可以为您工作,锁定争用较少(但我不太了解它,无法确定) .

注意:这假定您使用支持 SELECT ... FOR UPDATE 的 RDBMS;如果不是,请更新问题以告诉我们您使用的是哪个 RDBMS。

另一种选择是绕过 ORM 并执行 db.Exec("UPDATE counter_table SET counter = counter + 1 WHERE id = ?", 42)(不过请参阅 了解一些陷阱)。

一种可能的解决方案是使用 GORM 事务 (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)