淘宝打折  62折  淘宝折扣
随笔-51  评论-445  文章-0  trackbacks-4

快钱支付与Sql Server的乐观锁和悲观锁

 在实际的多用户并发访问的生产环境里边,我们经常要尽可能的保持数据的一致性。而其中最典型的例子就是我们从表里边读取数据,检查验证后对数据进行修改,然后写回到数据库中。在读取和写入的过程中,如果在多用户并发的环境里边,其他用户已经把你要修改的数据进行了修改是非常有可能发生的情况,这样就造成了数据的不一致性。

最近在做快钱支付的时候就碰到了这个问题,原来的代码如下:
1. 表Order的结构:
    OrderId   int 自增长
    Status   nvarchar(10)  //未处理时的状态为"wait"

2. 相关SQL语句:
Select Status from order where OrderID= @OrderID

Update Order set Status = 'Y' where OrderID=@OrderID

3.程式伪代码:
var status  = GetOrderStatus(orderid); //获取用户充值状态
if(status= "wait")//如果状态为未处理
   UpdateOrderStatus(orderid);//则更新状态为已处理
   //后台给用户充值的代码




按道理这样的代码是没有问题的,因为对同一个用户而已,并不存在并发的问题,也就不存在一次付款两次充值的问题。

然而快钱的处理方式是用户通过付款后,快钱要重新转到我们的网站来,我们在收到快钱支付成功的请求后,给用户充值,并将再次定向的页面返回给快钱,快钱再定向到支付成功的页面。
流程如下:用户--->GoToPay.aspx-->快钱-->AfterPay.aspx-->快钱-->AfterPayMessage.aspx。

由于快钱使用的是轮循的机制,会每隔一秒钟就访问AfterPay.aspx,因此会多次访问AfterPay.aspx,这时问题出来了:
var status  = GetOrderStatus(orderid); //获取用户充值状态
if(status= "wait")//如果状态为未处理
   UpdateOrderStatus(orderid);//则更新状态为已处理  
   //后台给用户充值的代码,会充值两次

在程式还未对Status更新的时候,第二次请求已经到达,这时使用GetOrderStatus,得到的还是"wait",因此会充值两次。



解决方案:
方式一:
使用常规的乐观锁方案
表Order里边加上一列TimeStamp 列,该列是varbinary(8)类型。但是在更新的时候这个值会自动增长。

Select Status,TimeStamp from order where OrderID= @OrderID

-- 更新状态,但是要比较时间戳是否发生了变化.如果没有发生变化,影响行数为1,更新成功.如果发生变化,影响行数为0。
update Order
set Status="Y",
where OrderID=@OrderID and TimeStamp=@timestamp
set @rowcount=@@rowcount


程式的修改
var status  = GetOrderStatus(orderid,out timestamp); //获取用户充值状态
if(status= "wait")//如果状态为未处理
   int rowcount = UpdateOrderStatus(orderid,timestamp);
if(rowcount= 1) //状态未更新
  充值
else //快钱第二次过来的时候,返回行数为0
 return   "已经充过值"
endif


方式二:
还是乐观锁方案:
由于表Order的Status本身就可以起到跟timestamp列一样的效果,修改如下:
update Order
set Status="Y",
where OrderID=@OrderID and Status="wait"
set @rowcount=@@rowcount

程式的修改
var status  = GetOrderStatus(orderid); //获取用户充值状态
if(status= "wait")//如果状态为未处理
   int rowcount = UpdateOrderStatus(orderid);
if(rowcount= 1) //状态未更新
  充值
else //快钱第二次过来的时候,返回行数为0
 return   "已经充过值"
endif
此方案更为简单。


方案三:
使用悲观锁的方式,这次修改的SQL语句不是Update 而是Select
如下:
Select Status   from order    with (UPDLOCK) where OrderID= @OrderID

程式完全不用修改:

//获取用户充值状态,快钱第二次过来的时候,如果第一次还未更新,则该订单行还处于锁定状态,因此会等待第一次更新完以将锁释放
var status  = GetOrderStatus(orderid);
if(status= "wait")//如果状态为未处理
   UpdateOrderStatus(orderid);//则更新状态为已处理
   //后台给用户充值的代码

这种方式最简单,程式完全不用修改,只需要在存储过程中加上with (UPDLOCK)即可。
缺点是对大量的并发性能会很差,而且会引起死锁。当然对于充值这种交易而言,还是可以比较适合的。



这是我写出来的第一篇技术类的文章,可能很多地方讲得不够透彻,希望大家指正。

下一篇文章我想就航空公司售票导致的并发问题提供一个更简单的解决方案。


 




posted on 2008-11-19 15:28 Ray Wu 阅读(2562) 评论(16) 编辑 收藏

评论:
#1楼 2008-11-19 15:42 | 问卷调查[未注册用户]
很好,很强大!
 回复 引用   
#2楼 2008-11-19 15:42 | 老破[未注册用户]
我是快钱返回处理时,判断我数据库里有没有存在返回的快钱号来实现防止后台重复处理充值。
 回复 引用   
#3楼 2008-11-19 15:47 | gude[未注册用户]
crwvtnyyuo,
 回复 引用   
#4楼[楼主] 2008-11-19 15:48 | Ray Wu      
@老破
这也是一个办法,当时我们处理的时候没有将快钱的相关信息保存下来,所以就通过乐观锁来解决了。
多谢老兄的提醒

 回复 引用 查看   
#5楼 2008-11-19 15:56 | 韦恩卑鄙      
@Ray Wu
大多数都是要跟双方的transid的
悲观锁太狠了

 回复 引用 查看   
#6楼[楼主] 2008-11-19 16:02 | Ray Wu      
@韦恩卑鄙
是的,第一次做支付方面的东西,没有经验,下次会注意了。

 回复 引用 查看   
#7楼 2008-11-19 16:29 | JL      
没太明白你的业务逻辑
但是使用锁定或者设置隔离级别一定能满足你

 回复 引用 查看   
#8楼[楼主] 2008-11-19 16:36 | Ray Wu      
@JL
因为我这个不是单纯的多用户并发,而是相当于一个用户提交多次引起的并发问题。

 回复 引用 查看   
#9楼 2008-11-19 16:41 | Tony Zhou      
good topic
 回复 引用 查看   
#10楼[楼主] 2008-11-19 16:55 | Ray Wu      
@Tony Zhou
非常感谢,第一次写这类文章。

 回复 引用 查看   
#11楼 2008-11-19 17:20 | Jeffrey Zhao      
嗯,的确想了解一下一个用户多次提交引起的问题。
 回复 引用 查看   
#12楼[楼主] 2008-11-19 17:37 | Ray Wu      
@Jeffrey Zhao
谢谢大哥,看你的文章,受益颇多。

 回复 引用 查看   
#13楼 2008-11-19 19:15 | Jeffrey Zhao      
@Ray Wu
不过其实你没有说这个话题阿……

 回复 引用 查看   
#14楼[楼主] 2008-11-20 00:38 | Ray Wu      
@Jeffrey Zhao
嗯,我这个是类似并发的问题,而不是重复提交。重复提交在某种情况下也会出现这个问题。

 回复 引用 查看   
#15楼 2008-11-20 18:45 | 怪怪      
这个话题和老赵说的重复提交有相似性。

类似的问题,似乎可以转化为自动机分析一下。

 回复 引用 查看   
#16楼 2010-01-20 22:22 | 浪迹上海滩      
希望能认识这么兄弟,我最近也在做支付平台,敬请加一下QQ群:61024604,交流交流。
 回复 引用 查看   
一直在做的一个

问卷调查

网站:问卷星 ,肯请大家多提点意见,多谢! 我
昵称:Ray Wu
园龄:5年8个月
粉丝:8
关注:13
<2008年11月>
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456

搜索

 

常用链接

我的标签

随笔分类(36)

随笔档案(52)

积分与排名

  • 积分 - 58619
  • 排名 - 1807

最新评论

阅读排行榜

评论排行榜

推荐排行榜