如何避免 GORM 中的竞争条件
How to avoid race conditions in 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
}
如@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 事务隔离保证。
更简洁的方式
总的来说,使用原子更新的计数器保持 table 个队列(按 reference_id)会更干净:
开始交易,然后
SELECT queue_number FROM queues WHERE registration_id = ? FOR UPDATE;
在您的应用程序代码中增加队列编号,然后
UPDATE queues SET queue_number = ? WHERE registration_id = ?;
现在您可以在事务提交前在您的患者 creation/update 中使用递增的队列号。
我正在开发一个系统,可以使用递增队列号进行患者登记。我正在使用 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
}
如@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 事务隔离保证。
更简洁的方式
总的来说,使用原子更新的计数器保持 table 个队列(按 reference_id)会更干净:
开始交易,然后
SELECT queue_number FROM queues WHERE registration_id = ? FOR UPDATE;
在您的应用程序代码中增加队列编号,然后
UPDATE queues SET queue_number = ? WHERE registration_id = ?;
现在您可以在事务提交前在您的患者 creation/update 中使用递增的队列号。