首页/ 学科题库/ gorm/ 悲观锁实现
gorm 实战教程

悲观锁实现

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 UPDATE、UPDATE、DELETE 操作会被阻塞,直到锁释放。

锁强度选项

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 排序),否则不同事务以不同顺序锁定相同行会导致死锁。

注意事项

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

要点总结

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

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

想在手机上练这篇的配套题目?
使用微信 卷王开发者小程序/App,首页顶部扫一扫识别下方二维码,即可解锁本讲对应真题与错题本。
了解产品 App
← 上一篇 GORM 读写分离实现 下一篇 → 乐观锁实现