实现关系型数据库乐观锁:何时何地检查行版本?
关系型数据库乐观锁实现方案解析
针对你提出的乐观锁实现问题,直接给你明确结论和最优方案:
一、版本比较的正确位置
版本比较应该直接放在UPDATE语句的WHERE条件中,利用数据库的原子操作保证并发场景下的数据一致性,这是实现乐观锁的标准做法。
不需要在应用层先查询数据库当前版本再做比较——这种方式不仅冗余,还可能在两次查询的间隙被其他线程修改数据,导致判断失效。
二、你提出的事务方案是否合适?
不合适。你给出的方案存在两个明显问题:
- 多了一次不必要的数据库查询,增加性能开销;
- 两次查询之间存在时间窗口,其他线程可能在这个窗口内修改数据,导致你拿到的版本已经过时,判断逻辑失去意义。
三、正确的实现方式
步骤1:获取用户数据(包含版本号)
SELECT id, name, version FROM users WHERE id = 1;
const jhon = get_user_by_id('1'); jhon.change_name_to('Jhin');
步骤2:在save方法中执行带版本校验的UPDATE
直接在UPDATE语句的WHERE条件里同时匹配id和获取到的version,同时将版本号自增:
function save(user) { // 注意:实际代码要做SQL注入防护,此处为简化写法直接拼接 const updateResult = query(` UPDATE users SET name = '${user.name}', version = ${user.version + 1} WHERE id = ${user.id} AND version = ${user.version}; `); // 通过受影响行数判断更新是否成功 if (updateResult.affectedRows === 0) { throw new Error("乐观锁冲突:该数据已被其他操作修改,请重试"); } }
原理说明
数据库的UPDATE操作是原子性的,执行这条语句时,数据库会同时检查id和version是否匹配:
- 如果匹配,说明没有其他线程修改过这条数据,执行更新并把版本号+1;
- 如果不匹配,说明数据已被修改,更新不会生效,受影响行数为0,此时在应用层抛出冲突异常即可。
如果业务需要多个操作原子执行,可以将UPDATE放在事务中,但核心逻辑依然是利用WHERE条件做版本校验,不需要额外查询版本号。
内容的提问来源于stack exchange,提问作者Roman Mahotskyi
相关产品推荐
相关产品推荐

