乐观锁需在UPDATE的WHERE条件中显式包含原始值(如version或rowversion),并检查影响行数是否为0;否则会静默覆盖他人修改。

UPDATE 时怎么知道这行数据被别人改过了
直接看 WHERE 条件里是否包含原始值(比如 version 字段或 updated_at 时间戳),这是最常用也最可控的乐观锁做法。数据库本身不自动帮你比对“修改前状态”,得你显式写进 SQL 里。
常见错误现象:UPDATE user SET name = 'Alice' WHERE id = 123 执行成功但没改出预期结果——因为别人先改了,你覆盖了他们的变更,且毫无感知。
- 必须把“上次读到的值”作为更新条件的一部分,例如
WHERE id = 123 AND version = 5 - 如果
ROW_COUNT()(MySQL)或pg_affected_rows()(PostgreSQL)返回 0,说明没匹配到行,即数据已被修改 - 不要依赖
SELECT ... FOR UPDATE后再 UPDATE——那是悲观锁,会阻塞,而且事务一长就容易死锁
SQL Server 的 rowversion 字段真能自动防覆盖吗
能,但它只负责“提供一个随每次更新自动变化的二进制标记”,不负责检查逻辑。你得自己拿它做条件判断,否则等于没用。
使用场景:适合不想手动维护 version 列、又希望有轻量级并发控制的业务表。
-
rowversion是自增二进制(不是时间),每次 UPDATE 都变,哪怕改的是同一值 - 正确用法是:
UPDATE doc SET title = 'New' WHERE id = 42 AND rv = 0x0000000000000A2F - 错误用法:只查不存
rv值,或 UPDATE 后不检查影响行数——rowversion不会抛异常,改失败就静默 - 注意兼容性:
rowversion在 SQL Server 中有效,但 PostgreSQL 用xmin或ctid,MySQL 没原生等价物
PostgreSQL 怎么用 xmin 实现类似乐观锁
xmin 是系统字段,记录插入该行的事务 ID,UPDATE 会生成新版本并更新 xmin,所以可用来判断“自上次读取后是否被改过”。但它不是为乐观锁设计的,用起来有坑。
性能影响小,但语义不严格:如果原事务已提交,xmin 就固定了;但如果原事务回滚,新插入的行 xmin 可能重用旧值——导致误判。
- 查询时记下:
SELECT id, name, xmin FROM account WHERE id = 100 - 更新时带上:
UPDATE account SET balance = 999 WHERE id = 100 AND xmin = 123456 - 别用
ctid做乐观锁——它指向物理位置,VACUUM 后可能失效 - 更稳妥的做法仍是加业务版号字段(如
version),xmin仅用于调试或临时校验
为什么不能只靠 SELECT ... FOR UPDATE 解决所有问题
它本质是加行锁,不是并发控制策略。在高并发、长事务或网络不稳定场景下,很容易变成瓶颈甚至引发死锁。
容易踩的坑:以为加了锁就安全,结果发现锁粒度不对、等待超时没处理、或者应用层没正确释放事务。
-
FOR UPDATE在事务提交/回滚前一直持有锁,其他事务会卡在waiting for lock - 如果前端请求超时但后端事务没关,锁就挂着,可能拖垮整个表
- 分布式环境下,跨服务无法共享数据库锁,
FOR UPDATE失效 - 真正需要的是“检测冲突 + 重试”逻辑,而不是“抢到锁就万事大吉”
复杂点在于:乐观锁要自己管理版本字段、处理重试、应对脏读边界;而悲观锁看似简单,实际对事务生命周期和超时控制要求更高。选哪条路,取决于你愿不愿意在应用层多写几行判断逻辑。










