[英]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 事务隔离保证。
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.