You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于Rails文档中悲观锁示例的竞态条件疑问

关于Rails悲观锁与竞态条件的问题

你的观察完全正确——如果在with_lock块外执行account = Account.first,确实会存在你描述的竞态条件,导致数据更新丢失。

为什么会出现问题?

当两个并发请求都在块外执行Account.first时,它们都会从数据库读取到初始的balance = 200并保存在内存中。第一个请求进入with_lock块后,会锁定这条记录并将余额修改为100,完成事务后释放锁。但第二个请求此时内存里的余额还是旧的200,它进入块后会基于这个旧值计算,最终将余额改成100而非正确的0,相当于丢失了一次扣减操作。

正确的解决方式

要彻底避免这种竞态,必须把查询操作和修改操作放在同一个锁定的事务中,确保查询时就锁定记录,不让其他事务读取到未提交的旧数据。有两种常见的正确写法:

写法1:使用带锁定的查询

直接在查询时就加上悲观锁,这样其他事务无法读取这条记录,直到当前事务完成:

account = Account.lock.first
account.with_lock do
  account.balance -= 100
  account.save!
end

或者更简洁的事务内写法:

Account.transaction do
  account = Account.lock.first
  account.balance -= 100
  account.save!
end

写法2:将查询放入with_lock块内

如果偏好with_lock的块语法,也可以把查询逻辑放进块中(注:此写法实际是表级锁,行级锁更推荐上面的方式):

Account.with_lock do
  account = Account.first
  account.balance -= 100
  account.save!
end

关键总结

悲观锁的核心是在读取数据时就锁定,避免其他事务读取到脏数据。如果先读取再锁定,中间的时间窗口会给并发请求留下读取旧数据的机会,从而引发竞态条件。

内容的提问来源于stack exchange,提问作者53c

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 00:26:09