UPDATE时version字段不生效的主因是未在WHERE条件中校验version或ORM未正确生成该条件;需确保SQL含WHERE version = ?、建立(id,version)联合索引、检查返回影响行数、重试时重新SELECT最新数据。

UPDATE 时 version 字段不生效的常见原因
乐观锁在 MySQL 中本质是靠应用层配合 version 字段做条件更新,不是数据库自动行为。如果 UPDATE 没加 WHERE version = ?,或者 WHERE 条件被绕过(比如用 id 单独更新),那并发修改就完全没锁住。
- 检查 ORM 是否生成了带
version的 WHERE 条件——MyBatis 需显式写AND version = #{version};Hibernate 要启用@Version并确保字段映射正确 - 避免手写 SQL 时漏掉
version判断,尤其在批量更新或关联更新场景 -
SELECT ... FOR UPDATE和乐观锁互斥,混用会掩盖问题:前者是悲观锁,后者靠失败重试,逻辑上不该共存 - 注意 MySQL 的
UPDATE返回影响行数:成功返回 1,冲突返回 0——必须检查这个值,而不是只看 SQL 是否执行成功
MySQL 中 version 字段该用什么类型
INT 最常用,但不是唯一解。关键看业务对“版本跳跃”和“溢出风险”的容忍度。
-
TINYINT UNSIGNED(0–255)适合生命周期短、更新极少的记录,比如配置开关类数据 -
INT UNSIGNED(0–4294967295)覆盖绝大多数场景,够用且兼容性好;BIGINT过重,除非单条记录每秒更新几十次以上 - 避免用
TIMESTAMP或DATETIME模拟 version:精度不足(如秒级)、时区问题、无法保证单调递增 - 不要用
UUID或字符串——无法原子自增,且索引效率差,WHERE 条件也难写
并发冲突后怎么重试才不卡死
乐观锁天然需要重试机制,但无脑 while 循环 + SELECT + UPDATE 容易打满 CPU 或触发死锁(尤其高并发下)。
- 重试前加随机短延迟(如
Thread.sleep(10 + new Random().nextInt(50))),避免所有线程在同一毫秒抢同一行 - 限制最大重试次数(通常 3–5 次),超过则抛业务异常(如
OptimisticLockException),由上游决定降级或提示用户 - 重试时必须重新
SELECT当前最新version和业务字段——不能复用旧值,否则第二次 UPDATE 还是会失败 - 注意事务边界:整个“查→算→更”必须包在同一个事务里,否则中间状态可能被其他事务改掉
WHERE version = ? 为什么有时还是漏判
MySQL 的 UPDATE 语句中,WHERE 条件看似写了 version,但实际执行时可能被优化器跳过,或因索引缺失导致全表扫描+误匹配。
- 确保
(id, version)有联合索引(或至少version单独有索引),否则 WHERE 无法高效定位,甚至出现幻读干扰判断 - 避免在 WHERE 中对
version做函数操作,如WHERE version + 0 = ?,会导致索引失效 - 注意 MySQL 严格模式:
sql_mode包含STRICT_TRANS_TABLES时,若传入 null 或非法值,UPDATE 可能直接报错而非静默失败 - 测试时别只用单线程模拟:真正并发冲突需用
sysbench或 JMeter 启多个连接,观察affected_rows是否真为 0
version 字段本身不解决并发问题,它只是把“冲突检测”从数据库推到应用层;真正容易被忽略的是:每次重试都要重新读取最新数据,而不是缓存旧 version 值反复尝试。










