天天看點

類似火車票出票的并發處理問題彙總

原始文章: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   日志