2015年4月15日 星期三

CFLOCK多人共用資源鎖定/獨占

WEB是個多人共用的執行環境。
好處是多人多工執行緒,不會互相干擾。
缺點是同時搶資源時處理上就很傷腦筋。

什麼叫『搶資源』,白話說搶票啦,簡單說就是希望讓多人執行可以排隊做"某件事"。

假如資料庫裡面有一個表Tickets,裡面裡面有500筆資料,每一筆都是獨立票號。
當客人要來取票時,系統會找出Tickets裡面沒有被使用的票號發給客人。
所以程式碼會寫成:
<cfquery name="check1" datasource="SQLDB">
 Select top 1 tick_no
 From Tickets
 Where tick_used = 'N'
</cfquery>

<cfif check1.recordcount GT 0>
 <cfset newTicket = check1.tick_no>
 <cfquery name="update1" datasource="SQLDB">
  Update Tickets
  Set tick_used = 'Y' , cust_id = '#LoginCust#'
  Where Tick_no = '#newTicket#'
 </cfquery>

</cfif>
<cftransaction action="commit" />
<cfif isdefined("newTicket")>
 <cfoutput>You Got Tick No : #newTicket#</cfoutput>
<cfelse>
 <cfoutput>No More Tickets can get !</cfoutput>
</cfif>

邏輯上看似沒有問題,但是請別忘記WEB是多人操作的,
結果就發生同時有2個人很湊巧的同一秒鐘按下取票,
因此上面程式碼就被兩個執行緒同時執行。

此時,兩個客人就會取到同一個票號 !!!

也許兩個執行緒有微秒之差,
但是在先進入的執行緒還沒有執行UPDATE前,第二個執行緒已經在執行SELECT了
所以,兩方都會取得相同的票號。

如何避免?

一、在SELECT前先執行SQL table lock,像是LOCK Tables...,但很遺憾,不是每一種資料庫都支援這樣的指令,當有一天要更換資料庫時,你的程式碼就得再大風吹一次,很可憐的.

二、在資料庫裡面寫個獨占式StoreProcedure,這個可以,但是你要再學資料庫的T-SQL語言,另外如果你公司很大,有獨立的DBA部門,搞不好DBA不想作這段程式碼,又得三請四託的很麻煩。

三、連線設定改成autocommit = false,這可能是大大大問題,應用程式也許這樣玩可以,但是WEB程式就不建議這樣作,其中有諸多原因,大致上說只要任一個使用者的commit沒執行,或者是程式碼忘了寫Commit就會大大大當機啦!!!

四、就是今天要介紹的,CFML有提供特殊指令CFLOCK,利用此指令可以鎖定(獨占)程式碼方塊、執行緒、甚至到整個網站都可以。所以現在就是利用這個指令來作比較方便。

我的作法,把需要動用到這個資料表程式碼便片段用CFLOCAK包起來(紅色)。
<cflock name="blockTickets" timeout="180" type="exclusive">
 <cfquery name="check1" datasource="SQLDB">
  Select top 1 tick_no
  From Tickets
  Where tick_used = 'N'
 </cfquery>

 <cfif check1.recordcount GT 0>
  <cfset newTicket = check1.tick_no>
  <cfquery name="update1" datasource="SQLDB">
   Update Tickets
   Set tick_used = 'Y' , cust_id = '#LoginCust#'
   Where Tick_no = '#newTicket#'
  </cfquery>

 </cfif>
 <cftransaction action="commit" />
 <cfif isdefined("newTicket")>
  <cfoutput>You Got Tick No : #newTicket#</cfoutput>
 <cfelse>
  <cfoutput>No More Tickets can get !</cfoutput>
 </cfif>
</cflock>
此處用的是程式碼區塊獨占方式,可以把這段寫到一個固定CFC裡面或是CFM裡面都可以,反正只要操作這個Tickets表的就是用這個區塊(不要有做其他區塊操作同樣的動作)。

CFLOCK設定TYPE="EXCLUSIVE"為獨占式,一次只允許一個執行緒執行。其他執行緒就是排隊等待被執行。
也就是說不管多少人執行這個網頁,只要遇到 blockTickets 這個 CFLOCK 區塊就是得排隊等待前面一個人執行結束。

所以,簡單的方式就可以解決搶票重複的問題了(大家都來排隊)。