乐观锁重试失败时,row_count()为0表示where条件不匹配,即版本已变导致更新被拦截;必须显式检查该值并分支处理,不可依赖“无报错”判断成功。

乐观锁重试失败时,ROW_COUNT() 为 0 怎么判断
MySQL 的 UPDATE 语句执行后,必须显式检查影响行数才能知道是否被乐观锁拦截。不能只靠返回“成功”就认为更新生效——因为 WHERE 条件不匹配时,语句本身语法正确、无报错,但实际影响 0 行。
常见错误是忽略这一步,直接 commit,导致业务逻辑以为扣减成功,实际库存没变。
- 在存储过程中用
IF ROW_COUNT() = 0 THEN ...做分支处理 - 在应用层(如 Java)调用
executeUpdate()后,检查返回值是否为 1(不是 0 或 >1) - 注意:某些 ORM 框架(如 MyBatis)默认不暴露
ROW_COUNT(),需手动配置或改用SqlSession.update()并捕获返回值
重试次数设为 3 次合理吗?要看冲突率和业务容忍度
固定重试次数(比如硬编码 3 次)是典型误区。真正该看的是当前场景下的版本冲突发生频率,以及单次重试带来的延迟是否可接受。
- 冲突率
- 冲突率 5%~10%:说明已超出乐观锁舒适区,应考虑改用悲观锁或拆分热点数据(如分库分表、库存分段)
- 每次重试都包含一次完整 round-trip(SELECT + UPDATE),网络+解析+锁等待(即使无锁也有 MVCC 版本比对开销)会累加,3 次重试可能带来 50ms~200ms 额外延迟
- 建议用指数退避(如 10ms、30ms、100ms)而非立即重试,避免雪崩式重试压垮数据库
为什么高并发下乐观锁反而比悲观锁更慢?
这不是理论矛盾,而是实际链路中被忽略的隐性成本。乐观锁“不加锁”的优势,只在冲突极少时成立;一旦冲突变多,重试带来的重复查询、重复解析、重复事务开销会快速反超悲观锁的持锁成本。
- 每次重试都触发一次新的事务(START TRANSACTION → SELECT → UPDATE → COMMIT/ROLLBACK),而悲观锁在一个事务内完成全部操作
- InnoDB 对带条件的
UPDATE仍需走索引查找 + 记录比对 + MVCC 版本校验,这部分开销不比SELECT ... FOR UPDATE小多少 - 如果表没建好索引(比如 WHERE 条件未命中索引),乐观锁的
UPDATE可能全表扫描比对 version,性能断崖下跌 - 日志写入量翻倍:3 次重试 = 3 次 binlog + redo log 写入,而悲观锁只有 1 次
重试逻辑放在应用层还是数据库存储过程里?
多数情况下,重试必须放在应用层。存储过程看似能减少网络往返,但会把业务耦合进数据库,且难以监控、灰度、降级。
- 存储过程重试:适合简单、原子、无外部依赖的操作(如纯库存扣减),但无法调用下游服务、发消息、记录审计日志
- 应用层重试:可控性强,可集成熔断(如 Sentinel)、异步补偿、人工干预入口;缺点是需要维护重试上下文(如原始请求参数、version 值)
- 关键点:重试时必须重新
SELECT获取最新version和业务字段(如 stock),不能复用第一次读到的旧值,否则第二次重试仍是拿过期 version 去比











