悲观锁如何防止更新丢失?基于Ask TOM的场景探讨
关于悲观锁场景下预防更新丢失的问题
首先明确:你描述的场景确实属于更新丢失——会话2基于提交前的旧数据生成更新语句,在获取锁后直接覆盖了会话1的变更,完全符合Ask TOM对更新丢失的定义:未感知到他人的变更就覆盖更新。
在悲观锁场景下,可以通过以下两种核心方式预防这类问题:
1. 遵循「先锁后读再更新」的完整流程
不要直接执行更新语句,而是先通过SELECT ... FOR UPDATE锁定目标行,再基于锁定后读取到的最新数据构造更新语句:
- 会话1执行:
SELECT address, phone FROM emp WHERE empno = 10 FOR UPDATE,锁定该行并读取当前数据 - 会话1基于读取到的数据修改后,执行:
UPDATE emp SET address = 'London', phone = 123 WHERE empno = 10,随后提交 - 会话2执行
SELECT ... FOR UPDATE时会被阻塞,直到会话1提交;此时会话2读取到的是会话1更新后的最新数据,再构造的更新语句就不会覆盖之前的变更
这种方式确保了所有更新操作都是基于锁定后的最新数据执行,从根源上避免了基于旧数据的覆盖更新。
2. 在更新语句中加入原始读取的字段条件(悲观锁+条件校验)
即使直接执行更新,也可以在WHERE子句中带上读取到的原始字段值,让数据库帮你校验数据是否被修改:
比如会话2读取到的原始地址是'OldAddr'、电话是'OldPhone',更新语句写成:
UPDATE emp SET address = 'Brighton', phone = 456 WHERE empno = 10 AND address = 'OldAddr' AND phone = 'OldPhone';
如果会话1已经修改了数据,这条语句会返回0行受影响,应用层可以捕获这个结果,提示用户「数据已被其他用户修改,请重新刷新后操作」。
这种方式相当于在悲观锁的基础上叠加了校验逻辑,既利用了悲观锁的阻塞特性,又避免了无意识的覆盖更新。
内容的提问来源于stack exchange,提问作者microwth
相关产品推荐
相关产品推荐

