跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

SQL 乐观锁 version 列的并发更新失败重试次数经验值设定

乐观锁重试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 —— 这些场景,重试次数再合理也救不回来。

相关文章