用 UPDATE ... WHERE version = ? 实现乐观锁需在表中添加单调递增的 version 字段,更新时 SET version = version + 1 并校验原 version,通过 RowsAffected() 判断是否更新成功,为防并发冲突应封装带限次重试(≤3 次)、每次重查最新 version 的函数。

用 UPDATE ... WHERE version = ? 实现乐观锁
Golang 本身不内置乐观锁机制,得靠 SQL 层配合版本号字段(比如 version 或 updated_at)来实现。核心逻辑是:更新时带上当前读到的版本号,数据库只在版本匹配时才执行更新,并返回影响行数。
常见错误是忽略 sql.Result.RowsAffected() 的检查,导致“以为更新成功”但实际被并发覆盖了。
- 必须在表里加一个单调递增的
version整型字段(初始值为1),每次更新都SET version = version + 1 - 更新语句形如:
UPDATE users SET name = ?, version = version + 1 WHERE id = ? AND version = ? - 执行后立刻调用
res.RowsAffected();若返回0,说明已有其他事务抢先更新,当前操作应失败或重试 - 不要用
updated_at替代version—— 时间精度低、可被篡改、时钟不同步时不可靠
在 Golang 中封装成可重试的更新函数
手动每次写 RowsAffected 判断容易漏,建议封装一层带重试逻辑的函数。但注意:重试不是无脑循环,要控制次数和间隔,避免雪崩。
- 重试次数建议 ≤ 3 次,第二次起加
time.Sleep(10 * time.Millisecond)避免忙等 - 每次重试前必须重新
SELECT当前记录(含最新version),不能复用旧值 - 别把重试逻辑塞进事务里——乐观锁本身就不依赖事务隔离级别,反而可能因长事务拖慢整体吞吐
- 示例关键片段:
for i := 0; i < maxRetries; i++ { err := db.QueryRow("SELECT version, name FROM users WHERE id = ?", id).Scan(&curVer, &curName) if err != nil { return err } res, err := db.Exec("UPDATE users SET name = ?, version = version + 1 WHERE id = ? AND version = ?", newName, id, curVer) if err != nil { return err } n, _ := res.RowsAffected() if n == 1 { return nil } // 成功 time.Sleep(backoff(i)) }
PostgreSQL vs MySQL 在 version 字段上的细微差别
两者都能支持乐观锁,但默认行为略有不同,会影响错误排查。
立即学习“go语言免费学习笔记(深入)”;
- MySQL 的
UPDATE即使WHERE不匹配也返回成功(err == nil),只能靠RowsAffected()判断;PostgreSQL 同样如此,别指望报错 - 如果用了 PostgreSQL 的
RETURNING,可以简化逻辑:UPDATE ... RETURNING version,若没返回行就说明失败 - MySQL 8.0+ 支持
SELECT ... FOR UPDATE SKIP LOCKED,但这属于悲观锁范畴,和乐观锁目标冲突,混用会增加死锁风险 - 别在
version上建唯一索引——它本就不唯一,且没必要
什么时候不该用 version 字段乐观锁
不是所有并发场景都适合。用错地方反而让代码更难维护、性能更差。
- 高频小字段更新(比如点赞数、阅读数)——此时更适合用原子操作:
UPDATE ... SET count = count + 1 WHERE id = ?,无需 version - 业务逻辑跨多张表、需强一致性——乐观锁只保单行,多表更新仍可能中间态不一致,该上分布式事务或 Saga
- 客户端长时间持有数据(比如编辑页面停留 10 分钟)——
version已严重过期,强制更新会频繁失败,应结合最后编辑时间做软校验 - ORM(如 GORM)自动注入
version时,务必确认它没悄悄帮你加了SELECT FOR UPDATE或其他隐式锁
version 字段看着简单,但真正跑起来,最容易被忽略的是「重试时没刷新数据」和「误把业务异常当并发冲突处理」——这两个点卡住过太多人。


















