简体   繁体   English

如何避免 GORM 中的竞争条件

[英]How to avoid race conditions in GORM

I am developing a system to enable patient registration with incremental queue number.我正在开发一个系统来启用具有增量队列号的患者注册。 I am using Go, GORM, and MySQL.我正在使用 Go、GORM 和 MySQL。

An issue happens when more than one patients are registering at the same time, they tend to get the same queue number which it should not happen.当多个患者同时注册时会出现问题,他们往往会获得相同的队列号,这是不应该发生的。

I attempted using transactions and hooks to achieve that but I still got duplicate queue number.我尝试使用事务和钩子来实现这一点,但我仍然得到重复的队列号。 I have not found any resource about how to lock the database when a transaction is happening.我还没有找到任何关于在事务发生时如何锁定数据库的资源。

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
}

As stated by @O.正如@O所说。 Jones, transactions don't save you here because you're extracting the largest value of a column, incrementing it outside the db and then saving that new value.琼斯,事务不会在这里保存您,因为您正在提取列的最大值,在数据库之外递增它,然后保存该新值。 From the database's point of view the updated value has no dependence on the queried value.从数据库的角度来看,更新的值不依赖于查询的值。

You could try doing the update in a single query, which would make the dependence obvious:您可以尝试在单个查询中进行更新,这将使依赖性变得明显:

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

In gorm you can't run a complex update like this, so you'll need to make use of Exec .在 gorm 中,您无法运行这样的复杂更新,因此您需要使用Exec

I'm not 100% certain the above will work because I'm less familiar with MySQL transaction isolation guarantees.我不是 100% 肯定上述方法会起作用,因为我不太熟悉 MySQL 事务隔离保证。

A cleaner way更清洁的方式

Overall, it'd be cleaner to keep a table of queues (by reference_id) with a counter that you update atomically:总的来说,保留一个队列表(通过reference_id)和一个你原子更新的计数器会更干净:

Start a transaction, then开始交易,然后

SELECT queue_number FROM queues WHERE registration_id = ? FOR UPDATE;

Increment the queue number in your app code, then增加应用代码中的队列号,然后

UPDATE queues SET queue_number = ? WHERE registration_id = ?;

Now you can use the incremented queue number in your patient creation/update before transaction commit.现在,您可以在事务提交之前在患者创建/更新中使用递增的队列号。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM