为了账号安全,请及时绑定邮箱和手机立即绑定

如何避免 GORM 中的竞争条件

如何避免 GORM 中的竞争条件

Go
皈依舞 2022-08-09 20:45:00
我正在开发一个系统,以启用具有增量队列编号的患者注册。我正在使用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}
查看完整描述

1 回答

?
炎炎设计

TA贡献1808条经验 获得超4个赞

正如 @O. Jones 所说,事务不会在这里保存你,因为你正在提取列的最大值,在 db 之外递增它,然后保存该新值。从数据库的角度来看,更新的值与查询的值无关。


您可以尝试在单个查询中执行更新,这将使依赖关系变得明显:


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 = ?;

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


查看完整回答
反对 回复 2022-08-09
  • 1 回答
  • 0 关注
  • 121 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号