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
相关产品推荐
相关产品推荐

