gorm 乐观锁需三步:加 optimisticlock.version 字段、读取时包含 version 值、更新时校验并自增;漏一步即失效,且必须检查 rowsaffected==0 并重试。

GORM 中实现乐观锁,核心就三步:加版本字段、读取时带 version、更新时自动校验。漏掉任意一步,乐观锁就形同虚设。
必须在 Model 中定义 optimisticlock.Version 字段
不是随便起个叫 Version 的 int 就行——GORM 的乐观锁插件只识别 optimisticlock.Version 类型。否则 Update 不会自动拼 WHERE version = ? 条件。
- 导入插件:
import "gorm.io/plugin/optimisticlock" - 字段声明必须是:
Version optimisticlock.Version(注意大小写和类型) - 字段名可以改(比如叫
OptimisticLock),但类型不能换;改名后需用gorm:column:xxx标签同步数据库列名 - 数据库中对应列建议用
INT UNSIGNED,避免负值或溢出
所有读取操作都得查出 Version 值
如果 SELECT 时没拿到 Version,后续更新必然失败——因为 WHERE 条件里的 version = ? 传的是 0 或零值。
- 禁止写
SELECT *后再手动忽略Version字段;也不要在 DAO 层做字段裁剪 - 列表页、详情页、编辑页加载数据,只要后续可能更新,就必须显式包含
Version - 用
db.Take()、db.First()、db.Find()都没问题,前提是 struct 字段已正确定义且非零值能被正确赋值 - 如果用
map[string]interface{}查询,记得手动提取"version"键并转成整型
Update 或 Updates 会自动注入乐观锁逻辑
只要 Model 里有正确的 Version 字段,且读取时拿到了有效值,GORM 就会在生成的 SQL 中自动加上 WHERE ... AND version = ? 并递增 version = version + 1。
- 检查
result.RowsAffected == 0是硬性要求,不能省;为 0 意味着冲突,必须重试或报错 - 不要自己手写
db.Where("version = ?", v).Updates(...)—— 这样会绕过插件逻辑,version 不会自增 - 批量更新(如
db.Model(&Blog{}).Where(...).Updates(...))不适用乐观锁,它无法保证每行 version 精确匹配 - 事务内慎用:如果先做了其他 DB 操作(如扣余额、发通知),最后才
Update,那前面的操作可能已基于过期数据执行,version 校验只是补救
最容易被忽略的点是:乐观锁只保单行原子性,它不解决业务层的时间窗口问题。比如你查出 version=5、balance=100,中间调了第三方接口花了 2 秒,别人早已把 balance 改成 50 并升到 version=6——你的更新会失败,但钱可能已经被扣了、日志已经写了。这种场景得靠更早的校验点或结合 SELECT FOR UPDATE。











