乐观锁重试3次最稳妥,超3次大概率非并发问题;必须检查row_count()或getupdatecount()判断是否更新成功;重试需加随机抖动;version宜用int而非timestamp或uuid。

乐观锁重试次数设为 3 次最稳妥
超过 3 次重试,大概率不是并发问题,而是业务逻辑卡点或数据竞争设计不合理。盲目加到 5 次、10 次,只会掩盖真实瓶颈,还可能把瞬时冲突拖成超时异常。
UPDATE ... SET version = version + 1 失败后必须检查 ROW_COUNT() 或影响行数
很多同学只捕获 SQL 异常(比如 SQLException),但乐观锁更新失败通常不抛异常——它安静地返回 0 行影响。不检查这个值,就等于没做乐观锁。
- MySQL 用
ROW_COUNT()判断是否更新成功 - PostgreSQL 用
RETURNING *或客户端获取updateCount - JDBC 中务必调用
PreparedStatement.getUpdateCount(),别只看executeUpdate()是否抛异常
重试间隔不能固定,要加随机抖动(jitter)
所有线程在失败后立刻重试,会形成“重试风暴”,反而加剧冲突。比如 100 个请求同时读到 version = 5,全部失败后立刻查最新 version 并再试——结果又撞在一起。
- 推荐策略:
Thread.sleep((long) (50 + Math.random() * 100)) - 避免使用
Thread.sleep(100)这种固定值 - 如果用 Spring Retry,配置
RandomBackOffPolicy,别用FixedBackOffPolicy
version 列类型选 INT 而非 BIGINT 或 TIMESTAMP
TIMESTAMP 做 version 看似能兼做更新时间,但会导致两个问题:一是高并发下精度不够(MySQL datetime 最小粒度是秒,datetime(6) 才微秒,但 ORM 不一定自动带精度);二是无法检测「无实质变更的重复提交」——两次更新内容一样,但时间戳变了,version 也变,锁就失效了。
-
INT UNSIGNED足够支撑千万级更新(最大值 42 亿) - 别用
UUID或哈希值模拟 version,那不是乐观锁,是乱序校验 - 确保 ORM 正确映射该字段为乐观锁控制列(如 MyBatis-Plus 的
@Version,Hibernate 的@Version)
真正难的不是设几次重试,而是分清:这次更新失败,到底是两个人真在改同一行,还是因为上游查了旧数据、中间被别人改过、或者自己逻辑漏刷新 version —— 这些场景,重试次数再合理也救不回来。










