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

乐观锁实现

GORM 乐观锁通过在数据表中添加版本号字段,在更新时校验版本号是否变化来检测冲突,适合读多写少、冲突概率低的场景。

版本号机制

表结构设计

数据表需包含 version 字段,每次更新时递增:

Go
type Product struct {
    ID      uint    `gorm:"primaryKey"`
    Name    string  `gorm:"size:100"`
    Stock   int
    Version int     `gorm:"default:1"` // 乐观锁版本号
}

手动实现乐观锁

通过 WHERE 条件校验版本号,确保更新时版本一致:

Go
func UpdateStockWithOptimisticLock(db *gorm.DB, id uint, newStock int) error {
    return db.Transaction(func(tx *gorm.DB) error {
        var product Product
        if err := tx.First(&product, id).Error; err != nil {
            return err
        }

        oldVersion := product.Version
        product.Stock = newStock
        product.Version++

        // UPDATE products SET stock=?, version=? WHERE id=? AND version=?
        result := tx.Model(&product).
            Where("version = ?", oldVersion).
            Updates(map[string]interface{}{
                "stock":   product.Stock,
                "version": product.Version,
            })

        if result.Error != nil {
            return result.Error
        }

        // 影响行数为 0 说明版本号已变化,存在并发冲突
        if result.RowsAffected == 0 {
            return errors.New("乐观锁冲突:数据已被其他事务修改,请重试")
        }

        return nil
    })
}

检查 RowsAffected == 0 是乐观锁的核心判断逻辑,说明查询到的版本号与当前数据库不一致。

重试机制

乐观锁冲突时应由业务层重试,而非直接报错:

Go
func UpdateWithRetry(db *gorm.DB, maxRetries int, id uint, newStock int) error {
    for i := 0; i < maxRetries; i++ {
        err := UpdateStockWithOptimisticLock(db, id, newStock)
        if err == nil {
            return nil
        }

        // 非乐观锁冲突错误,直接返回
        if !strings.Contains(err.Error(), "乐观锁冲突") {
            return err
        }

        // 指数退避重试
        time.Sleep(time.Duration(i+1) * 50 * time.Millisecond)
    }

    return fmt.Errorf("更新失败,已达最大重试次数 %d", maxRetries)
}

时间戳方案

除版本号外,也可使用 updated_at 时间戳实现乐观锁:

Go
type Order struct {
    ID        uint      `gorm:"primaryKey"`
    Status    string
    UpdatedAt time.Time `gorm:"autoUpdateTime"`
}

func UpdateOrderWithTimestamp(db *gorm.DB, id uint, newStatus string) error {
    var order Order
    if err := db.First(&order, id).Error; err != nil {
        return err
    }

    oldUpdatedAt := order.UpdatedAt
    result := db.Model(&Order{}).
        Where("id = ? AND updated_at = ?", id, oldUpdatedAt).
        Update("status", newStatus)

    if result.RowsAffected == 0 {
        return errors.New("数据已被修改")
    }

    return result.Error
}

适用场景对比

锁类型冲突成本适用场景性能特点
乐观锁低冲突时高读多写少无锁等待,吞吐量高
悲观锁高冲突时低写多或强一致行阻塞,并发度受限

注意事项

  • 冲突检测而非预防:乐观锁不阻止并发读写,仅在更新时检测冲突
  • 必须配合重试:冲突后应重新读取最新数据并重试,而非直接失败
  • 仅保护单行:多行更新无法通过单一版本号保证原子性
  • 版本字段类型:推荐使用 int 自增,避免时间戳精度问题导致误判
  • GORM 插件支持:gorm.io/plugin/optimistic 提供插件自动实现,减少手动编码

要点总结

  • 通过版本号字段 + WHERE version = ? 条件更新实现乐观锁
  • 使用 RowsAffected == 0 判断并发冲突
  • 业务层必须实现重试机制,配合指数退避策略
  • 适合低冲突场景,高冲突下性能反而不如悲观锁
  • 时间戳可作为版本号的替代方案,但精度需注意

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

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

← 上一篇 GORM 读写分离实现
下一篇 → 分布式锁集成
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

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

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