遭遇数据库并发问题

最近碰到一个数据库并发的问题, 觉得是一个很有意义的经验教训, 备忘一下.
大致过程是这样的:
在一次迭代发布之后, 用户反馈了一个问题: 虚拟商品交易成功, 但是买家没有收到卡密.而在日志中跟踪该笔交易, 正好有一个超时情况, 日志中打印出了程序执行的完整路径, 也就是说整个交易完全没有问题. 当时百思不得其解. 怀疑数据库出了问题. 又过了一天, 用户反映了同样的问题, 对比一下本次发布的代码修改, 里面有一个是关于sql的优化, 比较修改前后的代码, 终于找到了问题的罪魁祸首:原来是在sql上出现了问题.
最开始正确的sql:
update table set status =#newStatus#, trade_id = #tradeId#, buyer_id = #buyer_id# where id in (
select * from (
select id from table
where item_id = #itemId#
and status = #initStatus#
order by id
) where rownum <= #count#
) and status = #initStatus#

这句sql的意思是, 一个商品下面有多个虚拟物品(比如游戏币, 充值卡), 一个买家购买了该商品的多个虚拟物品, 此时应该将虚拟物品的状态设置为已购买状态, 因为这些虚拟物品会有多个人购买, 为了避免同一个虚拟物品被不同的人同时购买, 需要对要购买的虚拟物品排序, 然后从中获取要购买的数量, 最后更新状态.
修改后有问题的sql是这样:
update table set status =#soldStatus#, trade_id = #tradeId#, buyer_id = #buyer_id# where id in (
select * from (
select id from table
where item_id = #itemId#
and status = #initStatus#
order by id
) where rownum <= #count#
)

当时咨询dba, 认为在里面已经判断了状态, 外层sql的状态判断显得多余, 建议去掉.

虽然整个sql操作过程也是在事务中执行. 但是依然无法避免并发的出现, 经过分析, 基本上可以还原出问题的场景:
因为更新状态实际上分为两步, 首先要查出数据, 接着再更新. 当两个人同时购买同一个商品时. 如果两人同时查出同一批数据, 一人先更新, 退出事务(此时这批数据的状态已经发生改变), 接着另外一个人再次更新, 如果此时更新没有判断这批数据当前的状态(也就是做乐观锁), 那么就会覆盖前更新者的数据, 导致前一个没有获得最终购买的虚拟物品.
而外层的状态判断相当于对执行的sql加了一个乐观锁, 如果查询得到的那批数据被另一个请求更新, 则在更新状态的数量将与请求的数量不一致, 导致该次更新失败.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值