问题描述: 压力高并发情况下 执行update方法 得到结果非预期结果
例如: 多个线程执行 充值金额方法
1、先查询出目前金额信息a
2、将充值的金额组装a(原金额+充值金额=新金额)
3、更新金额(执行update方法)
问题: 多并发的情况下, 会是多个线程同时读取到目前金额a , 之后 多个线程进行分别组装数据,依次更新, 这就导致 A账户本来有50元 两个并发线程同时充值50元 最终数据库中 账户只有100元,而不是150元(第二次更新覆盖第一次更新)
解决方案:
1、先更新(更新会锁表) 然后在查询 如果金额超出预期 直接回滚 ,更新中 将加减运算放在sql中执行 可以保证数据准确性。
beginTranse(开启事务)
try{
//quantity为请求减掉的库存数量
$dbca->query('update s_store set amount = amount - quantity where postID = 12345');
$result = $dbca->query('select amount from s_store where postID = 12345');
if(result->amount < 0){
throw new Exception('库存不足');
}
}catch($e Exception){
rollBack(回滚)
}
commit(提交事务)
优化:
beginTranse(开启事务)
try{
//quantity为请求减掉的库存数量
$dbca->query('update s_store set amount = amount - quantity where postID = 12345');
$result = $dbca->query('select amount from s_store where postID = 12345');
if(result->amount < 0){
throw new Exception('库存不足');
}
}catch($e Exception){
rollBack(回滚)
}
commit(提交事务)
2、乐观锁,类CAS机制
第二种加锁方案是一种悲观锁机制。而且SELECT...FOR UPDATE方式也不太常用,联想到CAS实现的乐观锁机制,于是我想到了第三种解决方案:乐观锁。
具体来说也挺简单,首先SELECT SQL不作任何修改,然后在UPDATE SQL的WHERE条件中加上SELECT出来的vip_memer的end_at条件。如下:
vipMember = SELECT * FROM vip_member WHERE uid=1001 LIMIT 1 # 查uid为1001的会员
cur_end_at = vipMember.end_at
if vipMember.end_at < NOW():
UPDATE vip_member SET start_at=NOW(), end_at=DATE_ADD(NOW(), INTERVAL 1 MONTH), active_status=1, updated_at=NOW() WHERE uid=1001 AND end_at=cur_end_at
else:
UPDATE vip_member SET end_at=DATE_ADD(end_at, INTERVAL 1 MONTH), active_status=1, updated_at=NOW() WHERE uid=1001 AND end_at=cur_end_at
这样可以根据UPDATE返回值来判断是否更新成功,如果返回值是0则表明存在并发更新,那么只需要重试一下就好了。
3、应用层分布式锁
可以在应用层使用一个分布式锁(可以放在Memcache中),控制同一时间,只允许一个应用实例进行查询并更新的操作。
4、使用队列
相关推荐: http://blog.csdn.net/asd1836382/article/details/46355703
http://www.jb51.net/article/50103.htm
http://blog.csdn.net/dujianxiong/article/details/54849091
http://blog.csdn.net/caomiao2006/article/details/38568825(个人认为 这个写的还不错)