乐观锁需在UPDATE的WHERE子句中校验version字段,如UPDATE orders SET status='shipped',version=version+1 WHERE id=123 AND version=5;若影响行为0,说明已被并发修改,须重试。

UPDATE 语句里怎么加乐观锁条件?
乐观锁不是数据库自带的功能,而是靠你在 WHERE 子句里显式校验“数据没被改过”来实现。核心是:更新前检查当前记录的版本号(或时间戳、MD5 等)是否仍等于你读取时的值。
典型写法是:
UPDATE orders SET status = 'shipped', version = version + 1 WHERE id = 123 AND version = 5;
如果执行后 ROW_COUNT() 返回 0,说明有人抢先更新了,version 已变成 6 或更高,本次更新被拒绝。
- 必须确保
version字段有索引(至少是联合索引的一部分),否则WHERE条件可能全表扫描 - 不要用
SELECT ... FOR UPDATE混搭乐观锁——那属于悲观锁逻辑,反而抵消乐观锁的设计意图 - 如果业务允许“最后写入获胜”,就别用乐观锁;它只适用于“冲突需重试”的场景
用时间戳 or 版本号?哪个更靠谱?
优先选整数 version 字段,而不是 updated_at 时间戳。
-
updated_at在高并发下可能重复(尤其是毫秒级精度 + 多线程同毫秒写入),导致误判为“未被修改”而覆盖成功 -
version自增简单明确,无时区、精度、写入延迟等干扰 - 如果已有时间戳字段且无法加字段,可用
updated_at+ 主键组合做唯一性校验,但不如纯 version 稳定
UPDATE 后怎么判断是否更新成功?
不能只看 SQL 是否报错——乐观锁失败是静默的(影响行为为 0 行),必须主动检查影响行数。
- MySQL:用
mysql_affected_rows()或 PDO 的rowCount() - PostgreSQL:在
UPDATE后接RETURNING *,若返回空结果集则失败 - 注意 ORM(如 MyBatis、Hibernate)可能默认不暴露影响行数,需查文档确认是否支持
int update(...)返回值
为什么 WHERE 条件漏掉 version 就等于没锁?
这是最常踩的坑:代码里读出了 version = 5,但拼 SQL 时忘了塞进 WHERE,变成:
UPDATE orders SET status = 'shipped', version = 6 WHERE id = 123; -- ❌
这本质上是无条件覆盖,和没加锁一样。
- 建议把乐观锁条件封装成工具方法,避免手写 SQL 拼接
- 在测试中模拟并发:两个事务同时 SELECT 后 UPDATE,验证只有一个是
ROW_COUNT > 0 - 日志里打上原始
version值和实际影响行数,线上出问题时能快速定位是逻辑漏判还是真并发冲突
真正麻烦的不是写对那一行 WHERE version = ?,而是让整个业务流程接受“更新失败”并安全重试——比如订单状态流转中,重试前得先重新查最新状态,判断是否还需执行该操作。










