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

最近在做快钱支付的时候就碰到了这个问题,原来的代码如下:
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)即可。
缺点是对大量的并发性能会很差,而且会引起死锁。当然对于充值这种交易而言,还是可以比较适合的。


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

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

快钱支付与Sql Server的乐观锁和悲观锁的更多相关文章

  1. SQL Server 锁机制 悲观锁 乐观锁 实测解析

    先引入一些概念,直接Copy其他Blogs中的,我就不单独写了. 一.为什么会有锁 多个用户同时对数据库的并发操作时会带来以下数据不一致的问题: 1.丢失更新 A,B两个用户读同一数据并进行修改,其中 ...

  2. sql server对并发的处理-乐观锁和悲观锁

    https://www.cnblogs.com/dengshaojun/p/3955826.html sql server对并发的处理-乐观锁和悲观锁 假如两个线程同时修改数据库同一条记录,就会导致后 ...

  3. 在SQL Server里为什么我们需要更新锁

    今天我想讲解一个特别的问题,在我每次讲解SQL Server里的锁和阻塞(Locking & Blocking)都会碰到的问题:在SQL Server里,为什么我们需要更新锁?在我们讲解具体需 ...

  4. SQL并发处理方案——乐观锁和悲观锁

    (一)乐观锁和悲观锁的概念 悲观锁 在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)是一种并发控制的方法.它可以阻 ...

  5. SQL SERVER错误:已超过了锁请求超时时段。

    问题:远程连接数据库,无法打开视图,报错:SQL SERVER错误:已超过了锁请求超时时段. (Microsoft SQL Server,错误: 1222) 执行语句获取进程id select * f ...

  6. 通过DBCC Page查看在SQL Server中哪行数据被锁住了?

    原文:通过DBCC Page查看在SQL Server中哪行数据被锁住了? 如何查看被锁的是哪行数据?通过dbcc page可以. 要想明白这个问题: 首先,需要模拟阻塞问题,这里直接模拟了阻塞问题的 ...

  7. mysql的锁--行锁,表锁,乐观锁,悲观锁

    一 引言--为什么mysql提供了锁 最近看到了mysql有行锁和表锁两个概念,越想越疑惑.为什么mysql要提供锁机制,而且这种机制不是一个摆设,还有很多人在用.在现代数据库里几乎有事务机制,aci ...

  8. 《ASP.NET MVC4 WEB编程》学习笔记------乐观锁和悲观锁

    摘要:对数据库的并发访问一直是应用程序开发者需要面对的问题之一,一个好的解决方案不仅可以提供高的可靠性还能给应用程序的性能带来提升.下面我们来看一下Couchbase产品市场经理Don Pinto结合 ...

  9. Python知识之 方法与函数、偏函数、轮询和长轮询、流量削峰、乐观锁与悲观锁

    方法与函数 函数需要手动传参self.cls,方法自动传,比如对象方法自动传self,类方法自动传cls,而函数相对而言需要手动传,比如静态绑定的函数,self是需要手动传值得,比如我们平常使用的函数 ...

随机推荐

  1. Matlab2014下载和破解方法,以及Matlab很好的学习网站

    ISO镜像下载地址链接: http://pan.baidu.com/s/1i31bu5J 密码: obo1  单独破解文件下载链接: http://pan.baidu.com/s/1c0CGQsw 密 ...

  2. 很好的UI动效设计参考

    toolBar下拉:

  3. Centos7安装配置gitlab

    Centos7安装配置gitlab 这篇文字我会介绍在Centos7上安装gitlab,配置gitlab的smtp,并且创建项目demo. sudo yum install openssh-serve ...

  4. javascript简介和基本语法

    javascript简介 1.javascript是个脚本语言,需要有宿主文件,他的宿主文件是html文件. 用法:为了保险起见一般写在</html>之后<javascript   ...

  5. 用mciSendString做音乐播放器

    音乐操作类 public class clsMCI { public clsMCI() { // // TODO: 在此处添加构造函数逻辑 // } //定义API函数使用的字符串变量 [Marsha ...

  6. 【原创】Java实现手机号码归属地查询

    网络上已经有很多的手机号码归属地查询的API接口,但是这些接口总是有一些大大小小的缺陷. 总结一下这些缺陷: 1.要直接将它的搜索框链接形式粘到自己的页面,点击查询的时候还要跳转到他们的网站来展示归属 ...

  7. 文件输入流 FileInputStream类 文件输出流FileOutputStream ---转载

    转载自---http://blog.csdn.net/hguisu/article/details/7418161 111:文件输入流: FileInputStream可以使用read()方法一次读入 ...

  8. 关于点击ztree的节点将页面生成到easyui的新增选项卡(easyui-tabs)时,总是在浏览器中生成一个新的页面的问题

    最近的项目中用到了easyui,还有ztree菜单.在这里将我遇到的一些问题写出来算是做个笔记吧. 这是我头一次在博客园里分享代码,我的处女作,写的不好的地方还望各位见谅! 由于很久没有写过前台的东西 ...

  9. zoj 3229 Shoot the Bullet(无源汇上下界最大流)

    题目:Shoot the Bullet 收藏:http://www.tuicool.com/articles/QRr2Qb 把每一天看成一个点,每个女孩也看成一个点,增加源和汇s.t,源向每一天连上[ ...

  10. Ubuntu 安装libusb

    执行以下指令 sudo apt-get install libusb-dev sudo apt-get install libusb-1.0-0-dev