在Golang微服务中为所有实体实现可扩展的乐观并发控制(OCC)
Golang微服务乐观并发控制(OCC)可复用实现方案
一、自定义统一可扩展模块(推荐)
核心思路是将OCC的版本校验、事务控制逻辑封装为通用模块,各实体只需实现约定接口即可复用核心逻辑,同时保留自身独立的业务更新能力。
1. 定义OCC实体约定接口
先规范一套接口,明确实体需要实现的OCC相关能力:
import ( "database/sql" "fmt" ) // OCCEntity 所有需要支持OCC的实体需实现此接口 type OCCEntity interface { // GetCurrentVersion 从数据库查询当前实体的版本号(需在事务内执行) GetCurrentVersion(tx *sql.Tx) (int, error) // UpdateEntity 执行实体自身的业务更新逻辑(不含版本更新,需在事务内执行) UpdateEntity(tx *sql.Tx) error // GetRequestVersion 获取API请求携带的版本号 GetRequestVersion() int // UpdateVersion 更新数据库中实体的版本号(需在事务内执行) UpdateVersion(tx *sql.Tx, newVersion int) error }
2. 编写通用OCC执行函数
封装版本校验、事务管理、冲突处理的核心逻辑:
// ExecuteOCCUpdate 统一执行OCC更新逻辑,处理版本校验与事务 func ExecuteOCCUpdate(db *sql.DB, entity OCCEntity) error { tx, err := db.Begin() if err != nil { return err } // 确保事务异常时回滚 defer func() { if r := recover(); r != nil { _ = tx.Rollback() } }() // 步骤1:查询当前数据库中的版本号 currentVersion, err := entity.GetCurrentVersion(tx) if err != nil { _ = tx.Rollback() return err } // 步骤2:版本校验,不匹配则触发冲突 if currentVersion != entity.GetRequestVersion() { _ = tx.Rollback() return fmt.Errorf("concurrency conflict: db version %d != request version %d", currentVersion, entity.GetRequestVersion()) } // 步骤3:执行实体自身的业务更新 if err := entity.UpdateEntity(tx); err != nil { _ = tx.Rollback() return err } // 步骤4:更新版本号(以自增1为例,可根据需求调整规则) newVersion := currentVersion + 1 if err := entity.UpdateVersion(tx, newVersion); err != nil { _ = tx.Rollback() return err } // 提交事务 return tx.Commit() }
3. 实体适配示例
以User实体为例,实现接口并复用OCC逻辑:
type User struct { ID int Name string RequestVersion int // 从API请求中获取的版本号 } // GetCurrentVersion 查询User的当前版本 func (u *User) GetCurrentVersion(tx *sql.Tx) (int, error) { row := tx.QueryRow("SELECT version FROM users WHERE id = ?", u.ID) var version int err := row.Scan(&version) return version, err } // UpdateEntity 执行User的业务更新(修改名称) func (u *User) UpdateEntity(tx *sql.Tx) error { _, err := tx.Exec("UPDATE users SET name = ? WHERE id = ?", u.Name, u.ID) return err } // GetRequestVersion 返回请求携带的版本号 func (u *User) GetRequestVersion() int { return u.RequestVersion } // UpdateVersion 更新User的版本号 func (u *User) UpdateVersion(tx *sql.Tx, newVersion int) error { _, err := tx.Exec("UPDATE users SET version = ? WHERE id = ?", newVersion, u.ID) return err } // Update 对外暴露的User更新方法,直接复用通用OCC逻辑 func (u *User) Update(db *sql.DB) error { return ExecuteOCCUpdate(db, u) }
这种方式的优势:
- 核心OCC逻辑集中维护,新增实体只需实现接口即可快速接入
- 各实体保留独立的业务更新逻辑,不侵入原有代码结构
- 事务包裹全流程,避免脏读与数据不一致
二、第三方库适配方案(无ORM依赖)
由于遗留系统限制无法使用ORM,可选择轻量级SQL增强库辅助实现,无需修改现有查询逻辑:
1. 使用sqlx简化SQL操作
github.com/jmoiron/sqlx是对标准库database/sql的增强,支持结构体映射与简化查询,但不会改变原有SQL写法。可以结合上述自定义模块使用,仅用它简化实体的数据库操作,核心OCC逻辑仍由自定义模块控制。
示例调整(以User的GetCurrentVersion为例):
func (u *User) GetCurrentVersion(tx *sqlx.Tx) (int, error) { var version int err := tx.Get(&version, "SELECT version FROM users WHERE id = ?", u.ID) return version, err }
2. 驱动层无额外OCC支持
诸如mysql、postgres等官方驱动本身不提供OCC封装,因此仍需基于事务手动实现版本校验,本质与自定义模块思路一致,只是借助驱动的事务能力确保原子性。
关键注意事项
- 事务原子性:版本查询、业务更新、版本更新必须在同一个事务内执行,避免中间状态导致的数据不一致
- 版本字段选型:推荐使用整数自增(性能最优),也可使用UUID或时间戳(适用于分布式场景)
- 冲突处理:返回明确的冲突错误信息,方便客户端做重试、提示用户等处理
内容的提问来源于stack exchange,提问作者Ezio
相关产品推荐
相关产品推荐

