如何使用乐观锁防止数据冲突
并发情况下的数据冲突
mysql如果事物隔离级别为可重复读时,会出现以下问题。
假如商家A(id位1000)的某个商品受到了欢迎,被人疯狂下单,这时候商家A的钱应该是疯狂暴涨的。那A的钱应该怎么增加呢?
select balance from account where id=1000;
update account set balance=balance+price where id=1000;
特殊注明:所有的sql都只是为了表达意思,无法直接使用。
这个逻辑大概就是这样的,先查出来余额,然后将余额+价格存进去。
但是有一个问题,如果两个顾客同时下单会怎么样?
当查询完毕之后,比如两个事物查询到的结果都是10000,然后价格是500,这时候第一个赋值完成了10500,第二个赋值的时候依旧是10500。
这时候商家A不干了,你们买了两件商品,给我一份的钱。顾客也不干了,是是是,没给你加上,又不是没给我扣钱。凭空蒸发500元。
加锁?枷锁?
select balance from account where id=1000 for update;
update account set balance=balance+price where id=1000;
这相当于直接加了行锁,我访问的时候谁也别访问。
但是有个问题,如果高并发情况下,直接无脑加锁有点难搞啊。尤其是没有竞争的情况下,这不妥妥自己给自己上枷锁吗?
再举个例子,你这边加行锁了,这时候另一个请求仅仅想看看账户余额,比如商家A看看自己赚了多少钱,并不去修改,完了,废了,等着竞争锁吧。
乐观锁行吗?
乐观锁确实比悲观锁要好一点,但是实现起来可能需要费点劲。
首先增加一个version(版本号)字段,接下来select的时候获取一下版本号。
第二步,更新的时候,判断条件的时候,除了判断id,还要判断版本号是不是一样。
如果一样就更新,并且更新余额的时候把版本号+1.
如果不一样,那么就触发重试。
select balance,version from account where id=1000;
update account set balance=balance+price,set version=version+1 where id=1000 and version=version;
注意事项
可以使用spring-retry进行重试,如果更新不成功就去重试,几次之后还不成功就返回异常,让用户等会再试。避免频繁的重试引起更大的资源浪费。
update更新失败是通过返回值为0,即更新了0行让业务层感知到的,而引起update更新了0行,不一定就肯定是并发冲突啊,所以需要做好相应的判断。