全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-21 6 分钟 ✍️ juanwangdev

悲观锁实现

GORM 悲观锁通过 Clauses(clause.Locking{Strength: "UPDATE"}) 实现 SELECT FOR UPDATE,锁定查询行直到事务提交。

SELECT FOR UPDATE 语法

基本用法

在事务中使用 Clauses 添加行锁,其他事务读取被锁定行时会阻塞或跳过:

Go
func UpdateWithPessimisticLock(db *gorm.DB, id uint) error {
    return db.Transaction(func(tx *gorm.DB) error {
        var user User
        // SELECT * FROM users WHERE id = ? FOR UPDATE
        if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
            First(&user, id).Error; err != nil {
            return err
        }

        user.Balance -= 100
        return tx.Save(&user).Error
    })
}

FOR UPDATE 会锁定选中的行,其他事务的 SELECT FOR UPDATEUPDATEDELETE 操作会被阻塞,直到锁释放。

锁强度选项

Strength 值说明适用场景
UPDATE标准行锁,阻塞读写需要修改数据的场景
SHARE共享锁,允许并发读仅读取但需防止修改
Go
// 共享锁示例
tx.Clauses(clause.Locking{Strength: "SHARE"}).Find(&users)

// NOWAIT 选项:不等待,直接返回错误
tx.Clauses(clause.Locking{Strength: "UPDATE", Options: "NOWAIT"}).First(&user, id)

// SKIP LOCKED 选项:跳过已锁定行
tx.Clauses(clause.Locking{Strength: "UPDATE", Options: "SKIP LOCKED"}).Find(&users)

高并发场景应用

库存扣减

Go
func DeductStock(db *gorm.DB, productID uint, quantity int) error {
    return db.Transaction(func(tx *gorm.DB) error {
        var product Product
        // 锁定商品行,防止并发扣减
        if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
            First(&product, productID).Error; err != nil {
            return err
        }

        if product.Stock < quantity {
            return errors.New("库存不足")
        }

        product.Stock -= quantity
        return tx.Save(&product).Error
    })
}

余额操作

Go
func TransferMoney(db *gorm.DB, fromID, toID uint, amount float64) error {
    return db.Transaction(func(tx *gorm.DB) error {
        var from, to User

        // 按 ID 排序锁定,避免死锁
        if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
            Where("id IN (?, ?)", fromID, toID).Order("id ASC").Find(&[]User{}).Error; err != nil {
            return err
        }

        if err := tx.First(&from, fromID).Error; err != nil {
            return err
        }
        if from.Balance < amount {
            return errors.New("余额不足")
        }

        if err := tx.First(&to, toID).Error; err != nil {
            return err
        }

        from.Balance -= amount
        to.Balance += amount

        tx.Save(&from)
        return tx.Save(&to).Error
    })
}

多行锁定必须统一顺序(如按 ID 排序),否则不同事务以不同顺序锁定相同行会导致死锁。

注意事项

  • 事务必须显式开启:悲观锁仅在事务内生效,自动提交模式下无效
  • 锁持有时间尽量短:长事务会导致其他请求长时间阻塞,影响吞吐量
  • 死锁风险:多个事务交叉锁定不同行可能产生死锁,数据库会自动检测并回滚其中一个
  • 数据库兼容性:NOWAITSKIP LOCKED 仅 PostgreSQL/MySQL 8.0+ 支持,SQLite 不支持
  • 索引必须命中:无索引的全表扫描会锁定大量行甚至整表,严重影响性能

要点总结

  • 使用 Clauses(clause.Locking{Strength: "UPDATE"}) 实现 SELECT FOR UPDATE
  • 必须在事务内使用,确保数据强一致性
  • 多行操作按固定顺序锁定,避免死锁
  • 锁粒度越小越好,务必走索引缩小锁定范围
  • NOWAITSKIP LOCKED 适合排队和队列场景

存放路径:D:\git2\jwdev\articles\GORM\专家\高级并发与锁机制\悲观锁实现.md

📝 发现内容有误?点击此处直接编辑

← 上一篇 分布式锁集成
下一篇 → 死锁检测与处理
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库