用version字段+UPDATE WHERE id=? AND version=?+检查影响行数为0,是最可控易落地的防更新丢失方案;需显式校验version、SET中version=version+1、执行后必须判断返回值是否为0。

UPDATE 语句里怎么用 WHERE 校验 version 字段
乐观锁的核心就是:更新前确认数据没被别人改过。SQL 里最直接的方式,就是在 UPDATE 的 WHERE 子句里带上当前已知的 version 值。如果行已被其他事务更新,version 不匹配,这条 UPDATE 就不会影响任何行(rows affected = 0),应用层据此判断更新失败。
典型写法:
UPDATE orders SET status = 'shipped', version = version + 1 WHERE id = 123 AND version = 5;
注意点:
-
version必须是数值类型(如INT),且初始值不能为NULL - 必须在
SET中显式递增version,不能只靠WHERE判断 - 执行后检查返回的受影响行数——不是看是否报错,而是看是否为 0
为什么不能用 SELECT + UPDATE 两步实现
看似直观的“先查 version,再带 version 更新”,在并发下根本不可靠。两次操作之间存在时间窗口,别的事务可能已经完成修改并提交。
常见错误模式:
SELECT version FROM orders WHERE id = 123; -- 得到 version=5<br>UPDATE orders SET ... WHERE id = 123 AND version = 5; -- 这里可能已失效
问题在于:两次查询/更新之间没有事务隔离保障(即使加了 SELECT ... FOR UPDATE,就变成悲观锁了,违背乐观初衷)。真正安全的校验必须原子化——即单条 UPDATE 语句内完成读取旧值 + 比较 + 写入新值。
Java/JDBC 中如何可靠获取更新结果
JDBC 的 executeUpdate() 返回的是实际更新的行数,这是判断乐观锁是否成功的唯一依据。不要依赖异常、也不要默认认为“没报错就成功”。
示例逻辑片段:
int rows = stmt.executeUpdate("UPDATE account SET balance = ?, version = version + 1 " +<br> "WHERE id = ? AND version = ?");<br>if (rows == 0) {<br> throw new OptimisticLockException("version mismatch");<br>}关键细节:
- 参数顺序必须和 SQL 中占位符严格对应,尤其
version要传入“期望的旧值” - 不要用
getGeneratedKeys()或查询新 version 来验证——它可能根本没更新 - 如果业务需要最新 version,得额外
SELECT version,但那是下次操作的事
MySQL 和 PostgreSQL 在 version 自增上的细微差别
主流数据库都支持 SET version = version + 1,但对并发更新的处理逻辑一致。真正要注意的是初始化和约束:
- 建表时给
version加NOT NULL DEFAULT 0,避免首次更新因NULL + 1 → NULL失败 - PostgreSQL 对
UPDATE ... WHERE的执行计划更激进,但行为和 MySQL 一样:不匹配则零行影响 - 某些 ORM(如 MyBatis)会自动生成带 version 的 update,但务必确认它生成的是单条语句,而非先 select 再 update
version 字段本身不索引也行,但加个复合索引 (id, version) 能显著提升 WHERE 匹配效率——尤其是高并发更新同一主键时。

















