mysql - 如何避免 GORM 中的竞争条件

标签 mysql go transactions locking go-gorm

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

当不止一名患者同时挂号时会出现问题,他们往往会得到相同的队列号,这是不应该发生的。

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

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
}

最佳答案

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

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

关于mysql - 如何避免 GORM 中的竞争条件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66316123/

相关文章:

php - 如何定位Azure MySQL数据库连接字符串?

go - := mean in Go? 是什么

c# - 如果进程意外终止,作为 SqlTransaction 或 TransactionScope 基础的事务是否会回滚?

php - 建议,php mysql transaction for insertion to multiple tables in a block

java - SQL - 锁定或不锁定 : selecting next unused code

mysql - 在带有 group by 的 where 子句中使用别名

php - CakePHP 表与另一个表有两个关系

go - 如何使用[] byte作为Registry.GetValue中的缓冲区?

function - 通过对象而不是指向它的指针调用带有指针接收器的方法?

c# - 如何将 DateTimePicker 转换为日期以保存到 SQL Server?