龙空技术网

高并发下如何更新数据库数值类型字段

人人都是架构师 290

前言:

当前我们对“高并发修改数据”都比较珍视,大家都需要分析一些“高并发修改数据”的相关内容。那么小编也在网络上汇集了一些关于“高并发修改数据””的相关文章,希望兄弟们能喜欢,各位老铁们快快来学习一下吧!

用户购买商品的过程中,要对余额进行查询与修改,大致的业务流程如下:

第一步,从数据库查询用户现有余额:

SELECT money FROM t_yue WHERE uid=$uid;

不妨设查询出来的$old_money=100元。

第二步,业务层实施业务逻辑计算,比如:

(1)先查询购买商品的价格,例如是80元;

(2)再查询产品是否有活动,以及活动折扣,例如是9折;

(3)比对余额是否足够,足够时才往下走;

if($old_money> 80*0.9){

$new_money=$old_money-80*0.9=28

} else {

return "Not enough minerals";

}

第三步,将数据库中的余额进行修改。

UPDATE t_yue SET money=$new_money WHERE uid=$uid;

在并发量低的情况下,这个流程没有任何问题,原有金额100元,购买了80元的九折商品(72元),剩余28元。

同一个用户,并发扣款可能出现什么问题?

在分布式环境中,如果并发量很大,这种“查询+修改”的业务有一定概率出现数据不一致。

极限情况下,可能出现这样的异常流程:

步骤一,业务1和业务2并发查询余额,是100元。

步骤二,业务1和业务2并发进行逻辑计算,算出各自业务的余额,假设业务1算出的余额是28元,业务2算出的余额是38元。

步骤三,业务1对数据库中的余额先进行修改,设置成28元。

业务2对数据库中的余额后进行修改,设置成38元。

此时异常出现了,原有金额100元,业务1扣除了72元,业务2扣除了62元,最后剩余38元。

常见的解决方案?

1. 分布式悲观锁方案,但要引入额外的组件(redis/zk),并且会降低吞吐量。

2. 开启事务,在事务内执行select for update & update,这种依赖数据库的排它锁的方案对性能影响更加严重。

3. CAS乐观锁

使用CAS解决高并发时数据一致性问题,只需要在进行set操作时,compare初始值,如果初始值变换,不允许set成功。

具体到这个case,只需要将:

UPDATE t_yue SET money=$new_money WHERE uid=$uid;

升级为:

UPDATE t_yue SET money=$new_money WHERE uid=$uid AND money=$old_money;

(为了避免ABA问题,也可以不使用money,而是增加一个version字段)

即可。

并发操作发生时:

业务1执行:

UPDATE t_yue SET money=28 WHERE uid=$uid AND money=100;

业务2执行:

UPDATE t_yue SET money=38 WHERE uid=$uid AND money=100;

这两个操作同时进行时,只可能有一个执行成功。

怎么判断哪个并发执行成功,哪个并发执行失败呢?

set操作,其实无所谓成功或者失败,业务能通过affect rows来判断:

写回成功的,affect rows为1写回失败的,affect rows为0

失败以后业务可以继续”自旋“,在死循环中执行”查询余额,更新新余额“的操作,直到affect rows为1,或者余额小于扣款额的时候退出循环。

拓展

为什么一定要用select&set的方式进行余额写回:

UPDATE t_yue SET money=$new_money WHERE uid=$uid AND money=$old_money;

为什么不能采用直接扣减的方法:

UPDATE t_yue SET money=money-$diff WHERE uid=$uid;

很人说,在并发情况下,会将money扣成负数。

为了保证余额不被扣成负数,再加一个where条件:

UPDATE t_yue SET money=money-$diff WHERE uid=$uid AND money>$diff;

这样是否可行?

很遗憾,仍然不行。原因是不幂等。

什么是幂等性?

相同条件下,执行同一请求,得到的结果相同,才符合幂等性。

读请求,一般是幂等的。

写请求,视情况而定:

insert x,一般来说不是幂等的,重复插入得到的结果不一定一样delete x,一般来说是幂等的,删除多次得到的结果仍相同set a=x是幂等的set a=a-x不是幂等的

因此,这么扣减余额:

UPDATE t_yue SET money=$new_money WHERE uid=$uid AND money=$old_money;

是幂等操作。

要是这么扣减余额:

UPDATE t_yue SET money=money-$diff WHERE uid=$uid AND money>$diff;

不是幂等操作。

扣款怎么会重复执行呢?

重试。

重试,是异常处理里很常见的手段。

你在写业务的时候有没有写过这样的代码:

result = DoSomething();

if(false==result || TIMEOUT){

//错误,或者超时,重试一次

result= DoSomething();

}

return result;

当然,又会有朋友抬杠了,我从来不重试!!!

你可以决定业务代码怎么写,你不能决定底层框架代码怎么写:

(1)站点框架有没有自动重试?

(2)服务框架有没有自动重试?

(3)服务连接池,数据库连接池有没有自动重试?

画外音:

(1)服务化分层的架构中,建议只入口层重试,服务层不要重试,防止雪崩;

(2)dubbo底层,调用超时是默认重试的(failover),这个设计不好;

因此,在有重试的架构体系里,幂等性是需要考虑的一个问题。

总结:

扣款和充值业务,一般使用:

select&set,配合CAS方案

而不使用:

set money-=X方案

标签: #高并发修改数据