事務(業務事務/系統事務)並發策略選擇

來源:互聯網
上載者:User

 

樂觀鎖:

 

樂觀鎖樣本http://xmuzyq.javaeye.com/blog/295639
方式1.JDBC通過下面的語句:
Select  a.version....from Account  as a where (where condition..)
Update Account set version = version+1.....(another field) where version =?...(another contidition)

 

方式2.HIBERNATE:
public class Account{
    Long id ;
    .......
    @Version  //也可以採用XML檔案進行配置
    Int version
    .......
}
hibernate內部會產生相應的SQL語句將版本欄位加1,並且進行相應的版本檢測,如果檢測到並發樂觀鎖定異常,那麼就拋出StaleObjectStateException.

相比於樂觀離線鎖,樂觀鎖相當相當好理解。樂觀鎖與悲觀鎖都發生在一個系統事務的範圍內,不會象離線鎖跨N個系統事務。實現也是利用了原句1.樂觀鎖可以省去checkConcurrent.省去了儲存第一次請求後將verson放入http session。

比較1:
JDBC使用version欄位,hibernate使用@version註解。
hibernate的@version就是將使用JDBC情況下應用程式需要手動做的事情交給了架構自動化。比如jdbc事務中,涉及到並發修改的update sql語句同時需要手動更新version欄位的值(version=version+1 where version =?),進而判斷update影響的行數是否為1.如果不為1,說明在修改的過程中,有其它事務修改了該記錄,此時應用手動拋出自訂的樂觀鎖定異常(或者也可以採用spring封裝的異常體系),而hibernate減少了人為去做這樣的事情。它會自動產生帶version更新的update sql.

比較2:
version針對的資料來源是DB中的記錄。而針對記憶體中的對象,則使用鎖定賦值或CAS。version與CAS比較相似,一般CAS可以放到一個while迴圈裡,當compare=false的時候,重新從記憶體同步一個值過來,再進行CAS,直到在該線程賦值之前,沒有其它線程修改,然後賦值成功。參java_example。對於version,同樣可以捕獲並再次重新執行update操作,直到update返回的更新行數為1(即不會在提交更新的時候,發現已經被修改而拋出異常).

 

 

悲觀鎖:單個系統事務內,鎖定訪問的查詢記錄集。直到更新完畢後,才釋放鎖。一旦鎖定,別的系統事務如果要操作記錄集裡的部分記錄,則只能等待該系統事務釋放鎖定。 實現都是利用了DB提供的實現。
悲觀鎖較悲觀離線鎖簡單很多。悲觀離線鎖跨多個系統事務且需要應用程式來控制鎖的釋放和擷取。

悲觀鎖與交易隔離等級關係:
1.悲觀鎖可以加強事務的隔離等級。比如本身事務是低層級的讀取已提交或者更低,但是當事務使用了悲觀鎖之後,相當於在其基礎之後做了增加。變成可重複讀的層級。
具體參http://xmuzyq.javaeye.com/blog/295639
這很好理解,因為事務在第一次查詢之後,會對[[[選擇的行加排它鎖]]]。注意,並不是針對整個表加鎖。這樣當有新的insert以及delete非選中的記錄是允許的,這是出現幻影讀的原因,也指悲觀鎖不能增加到避免幻讀,如果能避免幻讀,相當於增加到了序列化層級。

2.如何避免幻影讀的問題
要想避免我們就需要設定資料庫隔離等級為Serializable,而一般情況下由於伸縮性都會採取讀取已提交或者更低隔離等級,同時配合樂觀或者悲觀鎖來實現並發控制。所以,幻讀問題在考慮效能的前提下是不能避免的。而幸運的是幻影讀問題一般情況下不嚴重。

DB實現悲觀鎖原理:
1、使用select ... where ... for update時,可以同時進行insert操作嗎?我嘗試過好像可以。
2、使用select ... where ... for update,只有執行commit後,才能繼續使用select ... where ... for update執行?如果同時有兩條:select ... where ... for update,那肯定只有一條在執行,當commit後,另外一條才能繼續執行?我嘗試過好像可以
規則是:FOR UPDATE語句將鎖住查詢結果中的元組,這些元組將不能被其他事務的UPDATE,DELETE和FOR UPDATE操作,直到本事務提交。
所以
1、可以,原因就是for update不會鎖定整個表,而只會鎖定select出來的行。
2、是的

 

比較1:樂觀鎖與悲觀鎖的選擇---成本
重新執行業務的成本與悲觀鎖定的成本(在java應用中使用synchronized導致的線程切換成本,而在資料庫事務裡則為使用悲觀鎖的成本)進行比較。如果前者大於後者,那麼就要選擇悲觀鎖,如果小於後者,那麼使用樂觀鎖。這與樂觀離線與悲觀離線的選擇是一樣的。悲觀鎖與樂觀鎖的選擇與鎖定還是CAS的成本比較相同,選擇鎖定還是CAS取決於線程切換的成本與業務執行的成本比較,比如一個計數器的設計,使用synchronized會導致線程的切換成本高於簡單的業務成功(累加)。而使用CAS的樂觀鎖定方式則將線程的切換成本降了下來。
比較2:悲觀鎖針對庫記錄,synchronized針對記憶體對象,二者非常相似。

 

悲觀鎖的樣本:http://xmuzyq.javaeye.com/blog/295639
1.JDBC中使用悲觀鎖:
每個衝突的事務中,我們必須使用select for update 語句來進行資料庫的訪問,如果一些事務沒有使用select for update語句,那麼就會很容易造成錯誤,這也是採用JDBC進行悲觀控制的缺點。
假如我們系統中有一個Account的類,我們可以採用如下的方式來進行:
Select * from Account where ...(where condition).. for update.

2.Hibernate中使用悲觀鎖:
在hibernate中使用悲觀鎖將會容易很多,因為hibernate有API讓我們來調用,從而避免直接寫SQL語句。
首先先要明確一下hibernate中支援悲觀鎖的兩種模式LockMode.UPGRADE以及LockMode.UPGRADE_NO_WAIT.(PS:在JPA中,對應的鎖模式是LockModeType.Read
假如我們系統中有一個Account的類,那麼具體的操作可以像這樣:
......
session.lock(account, LockMode.UPGRADE);
......
或者也可以採用如下方式來載入對象:
session.get(Account.class,identity,LockMode.UPGRADE).
這樣以來當載入對象時,hibernate內部會產生相應的select for update語句來載入對象,從而鎖定對應的記錄,避免其它事務並發更新。

 

 

樂觀離線鎖:每個記錄都會一個版本對象(表會有一個version的列,或者如果version資訊多的話,比如包含計算機等,可以單獨抽取成一個version表。與記錄的表有一個關聯關係。)。第一次讀取對象之後,版本號碼放入http session裡。以供第二次修改請求時,session取出頭一次的版本號碼與從資料庫重新讀取的版本號碼進行比較,如果一致,執行更新操作。

 

理解:checkConcurrent與系統事務最後一步的更新語句中的版本號碼的更新
還是以最簡化的二個請求的業務事務為例:第一個請求的系統事務將版本號碼存入http session。假設使用者在發送第二個請求之前,思考了N秒。發送第二個更新的請求。
最初的認識:
第二請求對應系統事務中的checkConcurrent成功,並且以為checkConcurrent與更新sql在一個系統事務裡,就認為更新sql就會成功。從而認為更新sql中的version=version+1隻是用來記錄版本號碼,沒其它作用----這是對帶版本更新的sql語句最大的誤解。
更新理解:
考慮到在情境中,有二個時間段可能會修改共用資料。第一個時間段是二個請求之間。第二個時間段發生在更新要求的系統事務的執行過程中,前提是交易隔離等級低於可重複讀。
所以checkConcurrent與sql中的version=version+1這就象一個雙保險一樣。checkConcurrent可以理解為第一保險,就象方法中一開始會有參數的校正一樣,第一時間判斷第一個時間段內是否有其它業務事務的系統事務修改了共用資料。進而判斷是否有接著執行的價值。version更新可看成是第二保險,在第二時間段內,如果事務的隔離等級低於可重複讀,其它的事務可能會修改共用資料,這樣帶版本號碼更新的sql語句可以進行最終的check。

馬丁的原話1:通常實現樂觀離線鎖是通過update或delete語句中加上版本號碼檢查來實現。
馬丁的原話2:可以將版本號碼比較的過程理解為擷取鎖,馬丁還講到如果沒有checkConcurrent,如果原話1的sql能成功執行,也可以理解為[[[擷取鎖並執行sql]]]。
我看POEAA,有時候,被這二個擷取鎖混淆,後來乾脆,版本號碼比較不稱為擷取鎖,而稱為checkConcurrent.用來在執行更新sql之前,先做一次攔截判斷,提前排除出現並發的修改的可能。1是樂觀離線鎖的核心原理。光使用1就完全可以實現樂觀離線鎖。

樂觀離線鎖可以阻上並發修改,但可能不能防止不一致讀:馬丁講並發會導致二個問題,一是並發修改,二是不一致讀。 不一致概念讀參閱POEAA 5.5.3。 對於樂觀離線鎖產生不一致讀的情境參建立帳單時讀使用者地址計算稅率。
樂觀離線鎖對不一致讀的解決:
一種是重讀版本號碼。類似於第一保險方式,同時保證事務的隔離等級必須是可重複讀以上。可重複讀的層級會在事務執行的過程中阻止其它使用者修改自己的地址,可以保證計算帳單的使用者的事務中讀到的使用者地址恒定的,也使用者修改地址之前的資料。
另一種方式是與解決並發更新的方式類似,對版本號碼進行增量操作。但是這無疑增加了複雜度,因為建單使用者只是想正確的讀一下使用者的地址,而不得不做地址更新的修改來輔助判斷不一致讀。
GYB:我覺得比較好的方式還是將該事務的隔離等級設定為可重複讀外加重讀版本號碼檢查地址資訊好理解。雖然可重複讀會影響一些效能。

隱式鎖類似於將原句1的動作陳述式由應用程式層抽象到了泛型DAO裡面進行。避免應用程式層開發人員寫sql時忘記加(version=version+1 where versoni=?)操作而導致的問題。

這樣的樂觀離線鎖是在應用程式中進行了版本比較。注意,checkConcurrent與更新操作必須要在一個系統事務裡才能保證一致性。如果將版本檢查直接綁定到update 或delete的語句中,則沒有這個問題。

 

樂觀離線鎖樣本:
1.
馬丁樂觀離線鎖的樣本中,沒有系統的實現一個業務事務下的樂觀離線鎖,比如沒有通過http sessoin儲存版本狀態,沒有實現第一保險。樣本圍繞如何使用原話1避免並發修改的問題。所有的更新語句的結果用來判斷是否擷取鎖成功(由於是第二保險,成功意識著擷取鎖並執行sql。看來以前的”擷取鎖“+更新sql中的"擷取鎖"要換個名稱了,比如checkConcurrent更合適。因為那個“擷取鎖”並不一定會讓更新sql一定成功,但能起到提前攔截的作用)。並且它希望由最初的應用程式層構造帶版本更新的sql語句抽取到層超類型(比如我們講的泛型DAO)。避免應用程式層的開發人員忘記構造帶版本更新的sql,統一由下面的持久層做這樣的工作。這也是隱式鎖概念。鎖對應用開發人員是透明的。

2.
http://xmuzyq.javaeye.com/blog/295639中舉例更適合理解樂觀離線鎖,一種是應用程式層控制,將checkConcurrent(版本比較)+更新sql放到一個系統事務裡(其實上面也提到裡面的checkConcurrent有沒有都可以,只要更新sql裡有version=version+1 where versoni=?,有checkConcurrent可以提前攔截並發修改的情況)。另一種方式,如果在持久層使用的hibernate,可以利用session.update(detach object),底層的實現原理應該與原句1類型。

 

 

問:如果一個業務事務包含多個系統事務,並且其中的多個有更新的操作,當最後一個修改事務提交失敗之後,之前提交成功的系統事務如何rollback呢?
最初理解:後來我覺得不應該存在這樣的一個業務事務。每個業務事務都應該是N-1個查詢+1個更新。所以,前面的情況解決為多個業務事務。

最後理解:通過閱讀hibernate文檔發現,如果之前的事務中包含了修改,可以設定session的FLUSHMODE=Never來避免commit的時候,把變化flush到資料庫,而在最後一次系統事務中,手動flush來把之前所有的更新一次性重新整理到庫,然後commit該系統事務。

 

 

悲觀離線鎖
悲觀離線鎖對共用記錄集可選三種鎖類型
1.獨佔寫鎖
獨佔寫鎖忽略了對資料的讀,因此不能保證讀出最新的資料。它用在二個業務事務同時修改一份共用資料時消除衝突。適用的情境是存在並發修改衝突,但系統對讀的要求不是很高比如允許讀不是最新的值,可以考慮使用獨佔寫鎖。
2.獨佔讀鎖
獨佔讀鎖“僅僅”為了讀出最新的資料才使用。顯然這種鎖會限制系統的並發性。畢竟通常系統讀遠遠大於寫,光為了讀新資料犧牲了這些效能,有些得不償失
獨佔寫鎖的效能強於獨佔讀鎖。
3.結合上面二種特點的讀寫鎖:讀鎖與寫鎖是互斥的。讀鎖可以並發持有。

 

明確了鎖之後,就可以定義鎖管理對象,它包括的屬性有:私人的鎖以及一個持有鎖的資料結構,這個資料結構可以對應一個記憶體散列表或是一張庫表。應用程式層只能通過鎖管理對象操作鎖,而不能直接存取鎖。

這個資料結構可以理解為key-value的結構,比如{業務事務1--鎖1,.......業務事務N--鎖N},業務事務N+1要使用鎖1,必須等到裡面的鎖1被業務事務1釋放才可以。
而由於不方便把業務事務做為參數傳給鎖管理器,則使用概念偷換,由於會話與業務事務可以等同理解。所以,可以把使用者的sessionid做為key.

樣本參POEAA

廣泛鎖定:如果一個客戶與一組地址資訊相關。如果有業務事務修改這組地址中的任意一個時,都需要將整個一組地址都鎖定住。(我的理解是避免其它業務對象修改了另一個不同地址並提交導致相互複蓋)而為每個地址對象加一個鎖很費事,此時考慮將使用者的version值設定為每個地址的versoin。這樣鎖定了使用者也就鎖定了它的地址集合。

POEAA第五章提到一些關於事務與並發策略的概念:

離線:系統的很多互動並不是在一個資料庫事務中能完成的。這就引出在跨事務的處理中管理好並發問題。這樣的問題稱為離線並發(offline concurrency)
並發修改最典型的情境是SCM。公司專屬應用程式中的並發修改情境是什麼呢? 多個使用者具有相同的許可權。而這些許可權會操作某個檔案或某個庫表。

事務可以是N條語句的執行單元。事務必須要有明確的開始與終止的指令定界。事務最佳的執行方式是一個請求對應一個事務。

業務事務不是說不能放入一個長的系統事務中執行,只是滿足了可用性,不能滿足延展性。

深刻理解事務的ACID
A:automicity
C:consistency
I:isolation
D:durability

系統事務的ACID(參POAEE)

業務事務的ACID(較少接觸)
最容易做到的是AD。A是因為當使用者點擊save按鈕,想要提交其所有的修改時,業務事務會先啟動一個系統事務。系統事務將保證被修改的資料將做為一個單元而提交,並將被持久化。唯一要擔心的是業務事務的生命週期內,維持一個正確的修改集。所以,AD可以保證。
業務事務最難保證的是I,因為沒有隔離性從而導致沒有一致性,一致性要求是不要將記錄集置於一種無效的狀態下。但是業務事務在其周期裡,存在被其它業務事務修改共用記錄集導致記錄集無效。

應該儘可能的讓事務系統自己來處理並發問題,跨系統事務並發將讓你掉入自己處理並發的黑水裡,裡面滿是攻擊性的動物。只有在不得已的時候,才將業務事務分成多個系統事務,如果能將業務事務放到單個系統事務中完成,就這樣做。如果可以忍受長事務帶來的延展性的問題,那就這樣做。通過把並發交易處理交給事務軟體(主要指資料庫)可以避免很多麻煩。

樂觀離線鎖的局限是當提交資料時,才發現業務事務將要失敗,而失敗的成本可能會很大。比如使用者花了一個小時輸入一份租約而導致提交失敗。(靈感,使用樂觀離線鎖時,如果要求使用者輸入過多的內容。可以要求提示使用者在內容提交之前先儲存一下,以便提交失敗後,copy重新提交)。優點是提供了最好的靈活性。
如果資料提交失敗的成本很大的情況下,才考慮使用悲觀離線鎖。它的優點是最早的時間探索資料已經被修改,否則在業務事務的生命週期內,其它業務事務是無法修改共用記錄集的,直至它提交成功。它的缺點是實現難度大,降低系統的靈活性。

 

除了關注的業務事務並發,還有另一類指應用伺服器的並發:與應用事務並發不一樣的地方不經過系統事務訪問共用的資料來源。而是針對記憶體對象中狀態的並發更新。
從隔離性最好的request/Process到效能最好的request/Thread到盡量每個請求建立一個新的業務對象。而不共用業務對象。象struts2中的方案一樣。

 

 

 

註:上面的筆記主要來自POEAA以及http://xmuzyq.javaeye.com/blog/295639的思考。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.