如何避免 GORM 中的竞争条件

我正在开发一个系统,以启用具有增量队列编号的患者注册。我正在使用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

}


皈依舞
浏览 154回答 1
1回答

炎炎设计

正如 @O. Jones 所说,事务不会在这里保存你,因为你正在提取列的最大值,在 db 之外递增它,然后保存该新值。从数据库的角度来看,更新的值与查询的值无关。您可以尝试在单个查询中执行更新,这将使依赖关系变得明显:UPDATE patient AS pJOIN (  SELECT max(queue_number) AS queue_number FROM patient WHERE registration_id = ?) maxpSET 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 = ?;现在,您可以在事务提交之前在患者创建/更新中使用递增的队列编号。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go