簡體   English   中英

如何避免 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 事務隔離保證。

更清潔的方式

總的來說,保留一個隊列表(通過reference_id)和一個你原子更新的計數器會更干凈:

開始交易,然后

SELECT queue_number FROM queues WHERE registration_id = ? FOR UPDATE;

增加應用代碼中的隊列號,然后

UPDATE queues SET queue_number = ? WHERE registration_id = ?;

現在,您可以在事務提交之前在患者創建/更新中使用遞增的隊列號。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM