乐观锁需在UPDATE语句中显式添加WHERE version = ?条件并检查受影响行数,为0则重试;version推荐INT/BIGINT类型;MyBatis中须手写XML SQL,Mapper方法返回int判断结果;重试前需重新查询最新数据,建议事务内执行。

UPDATE 时怎么用 version 字段做乐观锁
直接在 UPDATE 语句里加 WHERE version = ? 条件,更新成功说明没被并发修改,失败就重试。别指望数据库自动帮你比对版本——它只认你写的条件。
常见错误现象:UPDATE user SET name = 'A', version = version + 1 WHERE id = 123 AND version = 5 执行后 affected rows = 0,但程序没检查就继续往下走,结果丢了更新。
- 必须检查执行返回的受影响行数,
0表示版本已变,当前数据不可信 -
version字段类型推荐用INT或BIGINT,避免用TIMESTAMP(精度问题、时钟漂移) - 更新语句里要显式写
version = version + 1,不能先查再算再传进去(中间可能已被改)
MyBatis 中怎么安全地写带版本校验的 update
MyBatis 不会自动注入版本字段逻辑,得自己写 SQL 或用插件。手写 XML 是最可控的方式,别依赖 @SelectKey 或 useGeneratedKeys 去碰 version。
使用场景:Spring Boot + MyBatis 的业务服务层做用户资料更新、订单状态变更等需强一致性的操作。
- XML 中写完整条件:
UPDATE order SET status = #{status}, version = version + 1 WHERE id = #{id} AND version = #{version} - Mapper 接口方法返回
int,不是void,靠返回值判断是否更新成功 - 别用
@Param混合传递多个参数又不明确命名,容易把version传错成旧值或新值
UPDATE ... WHERE version = ? 失败后该怎么重试
重试不是无脑循环,得先重新读最新数据,再基于新 version 构造下一次更新。否则永远卡在旧版本上,形成“幻重试”。
性能影响:每次失败都多一次 SELECT,高并发下可能放大数据库压力;但比悲观锁全程锁表更轻量。
- 典型流程:查 → 改对象 → 更新(带 version)→ 失败?→ 再查 → 合并变更 → 重试,最多 3 轮
- 不要在事务外重试,否则两次
SELECT之间可能又被改,建议整个重试逻辑包在同一个事务里(或至少用REPEATABLE READ隔离级别) - 如果业务允许“最终一致”,可降级为记录冲突日志 + 异步补偿,而不是死磕重试
MySQL 和 PostgreSQL 对 CAS 更新的支持差异
标准 SQL 没有原生 CAS 语法,所有实现都靠 UPDATE ... WHERE + 行数判断,这点两者完全一致。差异出在并发行为和错误提示上。
兼容性影响:PostgreSQL 的 RETURNING 可以一次拿到更新后的 version,MySQL 8.0+ 才支持类似功能(VALUES(version)),老版本只能分两步。
- MySQL 5.7:必须
SELECT查当前version,再UPDATE,再检查affected rows - PostgreSQL:可用
UPDATE ... RETURNING version,失败时RETURNING不返回数据,比查行数更直观 - 注意 MySQL 的
autocommit默认开启,单条UPDATE就是独立事务,别误以为能跨语句看到自己刚写的 version
版本号乐观锁看着简单,真正难的是怎么把“查-判-改-再查”这几步串成原子逻辑,又不让重试变成雪崩。很多人卡在没检查 affected rows,或者把 version 当普通字段一样 set,忘了它本质是个“凭证”。










