【问题标题】:How to avoid race conditions in GORM如何避免 GORM 中的竞争条件
【发布时间】:2021-05-24 17:17:15
【问题描述】:

我正在开发一个系统,以启用具有递增队列编号的患者登记。我正在使用 Go、GORM 和 MySQL。

当多个患者同时注册时会出现问题,他们往往会获得相同的队列号,这是不应该发生的。

我尝试使用事务和挂钩来实现这一点,但我仍然得到重复的队列号。我还没有找到任何关于如何在事务发生时锁定数据库的资源。

func (r repository) CreatePatient(pat *model.Patient) error {
    tx := r.db.Begin()
    defer func() {
        if r := recover(); r != nil {
            tx.Rollback()
        }
    }()

    err := tx.Error
    if err != nil {
        return err
    }

    

    // 1. get latest queue number and assign it to patient object
    var queueNum int64
    err = tx.Model(&model.Patient{}).Where("registration_id", pat.RegistrationID).Select("queue_number").Order("created_at desc").First(&queueNum).Error

    if err != nil && err != gorm.ErrRecordNotFound {
        tx.Rollback()
        return err
    }
    pat.QueueNumber = queueNum + 1

    // 2. write patient data into the db
    err = tx.Create(pat).Error
    if err != nil {
        tx.Rollback()
        return err
    }

    return tx.Commit().Error
}

【问题讨论】:

  • 由于竞争条件,您不能安全地查找数据库中的最高 id 编号然后将其加一。交易没有帮助。当你在数据库中插入一行时,你会得到它的 ID 号。看看这个。 stackoverflow.com/questions/28773963/…
  • 很好的输入琼斯。但问题是我们使用 UUID 作为主键。即使我们使用增量主键,我们也不能依赖它来分配队列号,因为在一个表中有来自不同注册的患者。我们不希望患者根据不同登记的最后一个队列号来获取队列号。

标签: mysql go transactions locking go-gorm


【解决方案1】:

正如@O 所说。琼斯,事务不会在这里保存您,因为您正在提取列的最大值,在数据库之外递增它,然后保存该新值。从数据库的角度来看,更新后的值不依赖于查询的值。

您可以尝试在单个查询中进行更新,这会使依赖性变得明显:

UPDATE patient AS p
JOIN (
  SELECT max(queue_number) AS queue_number FROM patient WHERE registration_id = ?
) maxp
SET p.queue_number = maxp.queue_number + 1 
WHERE id = ?

在 gorm 中你不能运行这样复杂的更新,所以你需要使用Exec

我不能 100% 确定上述方法是否可行,因为我不太熟悉 MySQL 事务隔离保证。

更清洁的方式

总体而言,保留一个队列表(通过 reference_id)和一个您以原子方式更新的计数器会更简洁:

开始交易,然后

SELECT queue_number FROM queues WHERE registration_id = ? FOR UPDATE;

在您的应用代码中增加队列编号,然后

UPDATE queues SET queue_number = ? WHERE registration_id = ?;

现在您可以在事务提交之前在患者创建/更新中使用递增的队列号。

【讨论】:

  • 谢谢 Ezquiel,我以前没有考虑过。我试过了,但不知何故它返回错误Error 1093: You can't specify target table 'patients' for update in FROM clause
  • 是的,mysql 不喜欢查询正在更新的表的子查询,而是更新了 sql 以使用连接,并提供了我认为更好的整体方法。
  • 天哪,非常感谢!它就像一个魅力!我尝试使用“更脏”的方式,它按预期工作。
猜你喜欢
  • 2019-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-30
  • 2010-09-25
相关资源
最近更新 更多