Gorm原子更新增量计数器
Go Gorm Atomic Update to Increment Counter
我有一个本质上是用户可以递增的计数器。
但是,我想避免两个用户同时递增计数器的竞争条件。
有没有一种方法可以使用 Gorm 以原子方式递增计数器,而不是从数据库中获取值、递增并最终更新数据库?
如果您想使用基本的 ORM 功能,您可以在检索记录时使用 FOR UPDATE
作为 query option,数据库将锁定该特定连接的记录,直到该连接发出 UPDATE
查询以更改该记录。
SELECT
和 UPDATE
语句 必须 发生在同一个连接上,这意味着你需要将它们包装在一个事务中(否则 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)
我有一个本质上是用户可以递增的计数器。
但是,我想避免两个用户同时递增计数器的竞争条件。
有没有一种方法可以使用 Gorm 以原子方式递增计数器,而不是从数据库中获取值、递增并最终更新数据库?
如果您想使用基本的 ORM 功能,您可以在检索记录时使用 FOR UPDATE
作为 query option,数据库将锁定该特定连接的记录,直到该连接发出 UPDATE
查询以更改该记录。
SELECT
和 UPDATE
语句 必须 发生在同一个连接上,这意味着你需要将它们包装在一个事务中(否则 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)