You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Gorm结合PostgreSQL时,Select For Update的锁何时释放?

问题解答

核心结论

当前代码中,updateUser根本不需要(也没办法)释放getUser里的锁——因为这两个操作是完全独立的数据库事务,getUser执行完成后锁就已经被释放了。

原因分析

Gorm默认采用自动事务模式:每一次DB方法调用(比如First、Updates)都会自动开启一个临时事务,执行完SQL后立刻提交事务。所以:

  • getUser里的SELECT ... FOR UPDATE语句执行完毕,对应的临时事务就提交了,PostgreSQL会立刻释放这条行的锁。
  • 后续调用updateUser时,是在一个全新的独立事务里执行更新,这时候之前的锁早就不存在了。

潜在问题

这种拆分的写法会导致并发安全问题:从getUser拿到数据到updateUser执行更新的这段时间里,其他请求完全可以修改这条用户数据,最终导致你的更新覆盖掉别人的修改,出现数据不一致。

正确做法

必须将查询加锁和更新放到同一个显式事务中,这样SELECT ... FOR UPDATE的行锁会一直持有到事务提交/回滚,保证操作的原子性。

示例代码:

// 将查询加锁和更新逻辑封装到同一个事务中
func updateUserWithLock(ctx context.Context, repo *Repo, criteria interface{}) error {
    return repo.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
        // 1. 在事务内执行加锁查询
        var user model.User
        if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
            Preload("User").Where(criteria).First(&user).Error; err != nil {
            return err // 返回错误会触发事务回滚
        }

        // 2. 这里添加你的用户数据修改逻辑(比如更新字段值)
        // user.Nickname = "新昵称"
        // user.Age = 30

        // 3. 在同一个事务内执行更新
        if err := tx.Session(&gorm.Session{FullSaveAssociations: true}).
            Updates(&user).Error; err != nil {
            return err // 返回错误会触发事务回滚
        }

        // 返回nil则自动提交事务,锁会被释放
        return nil
    })
}

补充说明

  • 不需要手动调用tx.Begin()和tx.Commit():Gorm的Transaction方法会自动处理事务的开启、提交和异常回滚,比手动写更简洁安全。
  • 如果你的Preload("User")是笔误(比如应该是关联其他模型),需要关联表也加锁的话,可以在Preload时额外添加锁子句,但核心逻辑不变——所有操作必须在同一个事务内。

内容的提问来源于stack exchange,提问作者Mamen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 17:07:01