天天看点

类似火车票出票的并发处理问题汇总

原始帖子:http://topic.csdn.net/t/20060809/09/4936623.html 

问题要求:

 类似银行转账、火车票管理如订购等:   

  当select出一条或多条火车票后,选择其中一条记录上做一些操作,比如购买,此时如果有同样的一个人也同时定购了此火车票(此时并发),怎么办?  

  select纪录   ---〉购买处理过程   --〉票数-1  

  在第2步未完成,如果又有人做了第1步(比如恶意一直点击提交,或者刷屏),如何避免?

可参考的回答:

1、有下面三中并发控制策略可供选择:  

  Ø   什么都不做   –如果并发用户修改的是同一条记录,让最后提交的结果生效(默认的行为)    

  Ø   开放式并发(Optimistic   Concurrency)   -   假定并发冲突只是偶尔发生,绝大多数的时候并不会出现;   那么,当发生一个冲突时,仅仅简单的告知用户,他所作的更改不能保存,因为别的用户已经修改了同一条记录    

  Ø   保守式并发(Pessimistic   Concurrency)   –   假定并发冲突经常发生,并且用户不能容忍被告知自己的修改不能保存是由于别人的并发行为;那么,当一个用户开始编辑一条记录,锁定该记录,从而防止其他用户编辑或删除该记录,直到他完成并提交自己的更改    

  http://www.cnblogs.com/eddie005/archive/2006/08/01/OptimisticConcurrency.html   

 2、“查询-订票-收款-出票”是一个事务不假,但是它并不是一个1、2秒钟的数据库操作事务,而是一个持续较长时间(例如超过10秒钟)的业务。试想一下,如果一个终端在处理一张车票的时候所有其它几百的终端都被阻塞——“死机”——在这里了,或者幻读、赃读的终端每处理10次票仅能成功1次,而其它9次都会在操作员操作最后一步才出现提示“记录已经被修改,您的操作被回滚放弃,请重新执行订票流程”,这是多么让人懊恼的恶劣设计呀!  

  其实流程很简单,就是在订票开始就将票从“无人订购”表移到一个“正在处理”的表中。订票点的操作,收款、出票等,都是针对“正在处理”表中具体的票资源的操作,并不去动别人正在处理的票。订票点可以放弃处理从而把票放回“无人订购”的表中,也有可能后台会在超时(例如2分钟)之后自动处理这个。  

  事务锁不是一个业务处理的概念,而是一个处理软件编码的后期细节的技术。如果大家都把高级的业务流程当成低级的计算机技术来看待,会给用户带来无尽的烦恼,会产生大量成事不足的“错误地技术化了”的解决方案。

-------------------------------------------------

按照上面的的办法,就是不需要事务了,只需要增加一个表(正在处理表),a、当用户select表信息是从无人订购表里(原始表)里取数据   

 b、当用户选择某条纪录进行操作后,把此纪录相关信息存入"正在处理表"中,同时删除或修改在原始表里的纪录?待处理完毕,则删除“正在处理表”中的此纪录。   

 c、如果有并发情况,就需要查看"正在处理表"中是否有相同纪录,如果有,则不进行;其他人再查看的时候,读取的数据已经不是脏读数据了吧?  

  不知道理解正确不!?

3、我做的一个电信业务受理系统中电话号码选号模块就是这样实现的.  

  客户来选号时,将该号码移的状态标注为"正被选用",其它操作点就不能再选用这个号码,直到操作员取消对该号码的选择(用户撤消了业务办理请求)后,又将该号码的状态复位为"可以选用",其它操作点才可以再选用此号码,同时,数据库中有一个任务每5分钟检查一次号码表,如果一个号码被选用了5分钟之后状态仍然没有改变(操作点既没有受理装机业务,也没有释放该号码),则将号码的状态强制设置为"可以选用".  

  不建议用锁的方式,这样会导致其它操作点处于等待状态

4、有位说到业务和事务的问题,   如果业务占用时间多的话,   在多用户并发的情况下将此业务做进事务里对表进行并发控制是会产生效率问题.  

  但是楼主的意思是不知道并发如何控制,   比如:  

  甲售票点(甲事务)读出某航班的机票余额A,设A=16.    

  乙售票点(乙事务)读出同一航班的机票余额A,也为16.    

  甲售票点卖出一张机票,修改余额A←A-1.所以A为15,把A写回数据库.    

  乙售票点也卖出一张机票,修改余额A←A-1.所以A为15,把A写回数据库.  

  在此我举个例子:  

  首先请楼主在SQL查询分器起中执行如下语句:  

  if   exists(Select   OBJECT_ID(N'售票'))   drop   table   售票  

  go  

  create   table   售票  

  (票数量   integer)  

  insert   into   售票   values(50)   --初始化,使表中有50张票  

  go  

  if   exists(Select   OBJECT_ID(N'日志'))   drop   table   日志  

  go  

  create   table   日志  

  (日志   datetime)  

  go  

  if   exists(Select   OBJECT_ID(N'售票_ShortTime_Transaction'))   drop   proc   售票_ShortTime_Transaction  

  go  

  Create   Proc   售票_ShortTime_Transaction    

  as  

  begin   tran   --开始一个事务  

    insert   into   日志   values(GETUTCDATE())   --向日志表插入一条记录,证明已执行本事务  

    Declare   @tem_票数   integer  

    set   @tem_票数   =   (select   票数量   from     售票)  

    set   @tem_票数   =   @tem_票数   -   1  

    update   售票   set   票数量   =   @tem_票数  

  commit   --提交事务  

  go  

  if   exists(Select   OBJECT_ID(N'售票_LongTime_Transaction'))   drop   proc   售票_LongTime_Transaction  

  go  

  Create   Proc   售票_LongTime_Transaction  

  as  

  begin   tran  

    insert   into   日志   values(GETUTCDATE())   --向日志表插入一条记录,证明已执行本事务  

    Declare   @tem_票数   integer  

    set   @tem_票数=(select   票数量   from     售票)  

    Declare   @i   decimal  

    Set   @i=0  

    while(@i<500000)   --完成此循环我的计算机需要2s左右  

      set   @[email protected]+1  

    set   @tem_票数   =   @tem_票数   -   1  

    update   售票   set   票数量   =   @tem_票数  

    commit  

  go  

注:上面偷懒用了object_id所以第一次执行可能不成功要多执行几次:)  

  以上有两个存储过程一个叫   售票_ShortTime_Transaction,   它的作用是在较短的时间内完成一个事务,   所谓事务就是多个操作放一起做,   要么一起做要么都不做,   请参考教科书  

  还有一个事务叫   售票_LongTime_Transaction,由于用了一个循环所以它将读取了票数,并在较长时间后在对数据库进行修改.  

  ok,   现在我们在程序里面用两个线程来模拟甲乙同时购票,   其中甲用短时间事务,   乙用长时间事务.  

  private   SetData   conn1   =   new   SetData("Data   Source=.;Initial   Catalog=test1111;integrated   security=SSPI");   //SetData类是我自己的SQL操作类  

  private   SetData   conn2   =   new   SetData("Data   Source=.;Initial   Catalog=test1111;integrated   security=SSPI");//SetData类是我自己的SQL操作类  

  private   void   售票_短时间处理()  

  {  

  conn1.ExeNoneQuery("售票_ShortTime_Transaction");//执行存储过程  

  }  

  private   void   售票_长时间处理()//执行存储过程  

  {  

  conn2.ExeNoneQuery("售票_LongTime_Transaction");  

  }  

  private   void   button2_Click(object   sender,   System.EventArgs   e)  

  {  

  Thread   甲   =   new   Thread(new   ThreadStart(售票_短时间处理));  

  Thread   乙   =   new   Thread(new   ThreadStart(售票_长时间处理));  

  甲.Start();  

  乙.Start();  

  }  

  当点击按钮button2的之后,   请楼主再到查询分析起里面执行如下语句,查看结果:  

  select   *   from   售票  

  select   *   from   日志