樂觀鎖悲觀鎖

鎖( locking  
業務邏輯的實現過程當中,每每須要保證數據訪問的排他性。如在金融系統的日終結算 
處理中,咱們但願針對某個 cut-off 時間點的數據進行處理,而不但願在結算進行過程當中 
(多是幾秒種,也多是幾個小時),數據再發生變化。此時,咱們就須要經過一些機 
制來保證這些數據在某個操做過程當中不會被外界修改,這樣的機制,在這裏,也就是所謂 
    ,即給咱們選定的目標數據上鎖,使其沒法被其餘程序修改。 
Hibernate
 支持兩種鎖機制:即一般所說的  悲觀鎖( Pessimistic Locking  
  樂觀鎖( Optimistic Locking    
悲觀鎖( Pessimistic Locking  
悲觀鎖,正如其名,它指的是對數據被外界(包括本系統當前的其餘事務,以及來自 
外部系統的事務處理)修改持保守態度,所以,在整個數據處理過程當中,將數據處於鎖定 
狀態。悲觀鎖的實現,每每依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能 
真正保證數據訪問的排他性,不然,即便在本系統中實現了加鎖機制,也沒法保證外部系 
統不會修改數據)。 
一個典型的倚賴數據庫的悲觀鎖調用: 
select * from account where name=」Erica」 for update
這條 sql 語句鎖定了 account 表中全部符合檢索條件( name=」Erica」 )的記錄。 
本次事務提交以前(事務提交時會釋放事務過程當中的鎖),外界沒法修改這些記錄。 
Hibernate
 的悲觀鎖,也是基於數據庫的鎖機制實現。 
下面的代碼實現了對查詢記錄的加鎖: java

 

String hqlStr =
"from TUser as user where user.name='Erica'";
Query query = session.createQuery(hqlStr);
query.setLockMode("user",LockMode.UPGRADE); //
 加鎖 
List userList = query.list();//
 執行查詢,獲取數據 
query.setLockMode
 對查詢語句中,特定別名所對應的記錄進行加鎖(咱們爲 
TUser
 類指定了一個別名 「user」 ),這裏也就是對返回的全部 user 記錄進行加鎖。 
觀察運行期 Hibernate 生成的 SQL 語句: 
select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id
as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex
from t_user tuser0_ where (tuser0_.name='Erica' ) for update
這裏 Hibernate 經過使用數據庫的 for update 子句實現了悲觀鎖機制。 
Hibernate
 的加鎖模式有: 
Ø LockMode.NONE 
 無鎖機制。 
Ø LockMode.WRITE 
 Hibernate  Insert  Update 記錄的時候會自動 
獲取。 
Ø LockMode.READ 
 Hibernate 在讀取記錄的時候會自動獲取。 
以上這三種鎖機制通常由 Hibernate 內部使用,如 Hibernate 爲了保證 Update
過程當中對象不會被外界修改,會在 save 方法實現中自動爲目標對象加上 WRITE 鎖。 
Ø LockMode.UPGRADE 
:利用數據庫的 for update 子句加鎖。 
Ø LockMode. UPGRADE_NOWAIT 
 Oracle 的特定實現,利用 Oracle  for
update nowait
 子句實現加鎖。 
上面這兩種鎖機制是咱們在應用層較爲經常使用的,加鎖通常經過如下方法實現: 
Criteria.setLockMode
Query.setLockMode
Session.lock
注意,只有在查詢開始以前(也就是 Hiberate 生成 SQL 以前)設定加鎖,纔會 
真正經過數據庫的鎖機制進行加鎖處理,不然,數據已經經過不包含 for update
子句的 Select SQL 加載進來,所謂數據庫加鎖也就無從談起。 
樂觀鎖( Optimistic Locking  
相對悲觀鎖而言,樂觀鎖機制採起了更加寬鬆的加鎖機制。悲觀鎖大多數狀況下依 
靠數據庫的鎖機制實現,以保證操做最大程度的獨佔性。但隨之而來的就是數據庫 
性能的大量開銷,特別是對長事務而言,這樣的開銷每每沒法承受。 
如一個金融系統,當某個操做員讀取用戶的數據,並在讀出的用戶數據的基礎上進 
行修改時(如更改用戶賬戶餘額),若是採用悲觀鎖機制,也就意味着整個操做過 
程中(從操做員讀出數據、開始修改直至提交修改結果的全過程,甚至還包括操做 
員中途去煮咖啡的時間),數據庫記錄始終處於加鎖狀態,能夠想見,若是面對幾 sql

百上千個併發,這樣的狀況將致使怎樣的後果。 
樂觀鎖機制在必定程度上解決了這個問題。樂觀鎖,大可能是基於數據版本 
 Version )記錄機制實現。何謂數據版本?即爲數據增長一個版本標識,在基於 
數據庫表的版本解決方案中,通常是經過爲數據庫表增長一個 「version」 字段來 
實現。 
讀取出數據時,將此版本號一同讀出,以後更新時,對此版本號加一。此時,將提 
交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,若是提交的數據 
版本號大於數據庫表當前版本號,則予以更新,不然認爲是過時數據。 
對於上面修改用戶賬戶信息的例子而言,假設數據庫中賬戶信息表中有一個 
version
 字段,當前值爲 1 ;而當前賬戶餘額字段( balance )爲 $100  
操做員 此時將其讀出( version=1 ),並從其賬戶餘額中扣除 $50
 $100-$50 )。 
在操做員 A 操做的過程當中,操做員 B 也讀入此用戶信息( version=1 ),並 
從其賬戶餘額中扣除 $20  $100-$20 )。 
操做員 A 完成了修改工做,將數據版本號加一( version=2 ),連同賬戶扣 
除後餘額( balance=$50 ),提交至數據庫更新,此時因爲提交數據版本大 
於數據庫記錄當前版本,數據被更新,數據庫記錄 version 更新爲 2  
操做員 B 完成了操做,也將版本號加一( version=2 )試圖向數據庫提交數 
據( balance=$80 ),但此時比對數據庫記錄版本時發現,操做員 B 提交的 
數據版本號爲 2 ,數據庫記錄當前版本也爲 2 ,不知足  提交版本必須大於記 
錄當前版本才能執行更新  的樂觀鎖策略,所以,操做員 的提交被駁回。 
這樣,就避免了操做員 用基於 version=1 的舊數據修改的結果覆蓋操做 
 A 的操做結果的可能。 
從上面的例子能夠看出,樂觀鎖機制避免了長事務中的數據庫加鎖開銷(操做員 A
和操做員 B 操做過程當中,都沒有對數據庫數據加鎖),大大提高了大併發量下的系 
統總體性能表現。 
須要注意的是,樂觀鎖機制每每基於系統中的數據存儲邏輯,所以也具有必定的局 
限性,如在上例中,因爲樂觀鎖機制是在咱們的系統中實現,來自外部系統的用戶 
餘額更新操做不受咱們系統的控制,所以可能會形成髒數據被更新到數據庫中。在 
系統設計階段,咱們應該充分考慮到這些狀況出現的可能性,並進行相應調整(如 
將樂觀鎖策略在數據庫存儲過程當中實現,對外只開放基於此存儲過程的數據更新途 
徑,而不是將數據庫表直接對外公開)。 
Hibernate 
在其數據訪問引擎中內置了樂觀鎖實現。若是不用考慮外部系統對數 
據庫的更新操做,利用 Hibernate 提供的透明化樂觀鎖實現,將大大提高咱們的 
生產力。 
Hibernate
 中能夠經過 class 描述符的 optimistic-lock 屬性結合 version
描述符指定。 
如今,咱們爲以前示例中的 TUser 加上樂觀鎖機制。 數據庫

1  首先爲 TUser  class 描述符添加 optimistic-lock 屬性: 
<hibernate-mapping>
<class
name="org.hibernate.sample.TUser"
table="t_user"
dynamic-update="true"
dynamic-insert="true"
optimistic-lock="version"
>
……
</class>
</hibernate-mapping>
optimistic-lock
 屬性有以下可選取值: 
Ø none
無樂觀鎖 
Ø version
經過版本機制實現樂觀鎖 
Ø dirty
經過檢查發生變更過的屬性實現樂觀鎖 
Ø all
經過檢查全部屬性實現樂觀鎖 
其中經過 version 實現的樂觀鎖機制是 Hibernate 官方推薦的樂觀鎖實現,同時也 
 Hibernate 中,目前惟一在數據對象脫離 Session 發生修改的狀況下依然有效的鎖機 
制。所以,通常狀況下,咱們都選擇 version 方式做爲 Hibernate 樂觀鎖實現機制。 
2
  添加一個 Version 屬性描述符 
<hibernate-mapping>
<class
name="org.hibernate.sample.TUser"
table="t_user"
dynamic-update="true"
dynamic-insert="true"
optimistic-lock="version"
>
<id
name="id"
column="id"
type="java.lang.Integer"
>
<generator class="native">
session

</generator>
</id>
<version
column="version"
name="version"
type="java.lang.Integer"
/>
……
</class>
</hibernate-mapping>
注意 version 節點必須出如今 ID 節點以後。 
這裏咱們聲明瞭一個 version 屬性,用於存放用戶的版本信息,保存在 TUser 表的 
version
 字段中。 
此時若是咱們嘗試編寫一段代碼,更新 TUser 表中記錄數據,如: 
Criteria criteria = session.createCriteria(TUser.class);
criteria.add(Expression.eq("name","Erica"));
List userList = criteria.list();
TUser user =(TUser)userList.get(0);
Transaction tx = session.beginTransaction();
user.setUserType(1); //
 更新 UserType 字段 
tx.commit();
每次對 TUser 進行更新的時候,咱們能夠發現,數據庫中的 version 都在遞增。 
而若是咱們嘗試在 tx.commit 以前,啓動另一個 Session ,對名爲 Erica 的用 
戶進行操做,以模擬併發更新時的情形: 
Session session= getSession();
Criteria criteria = session.createCriteria(TUser.class);
criteria.add(Expression.eq("name","Erica"));
Session session2 = getSession();
Criteria criteria2 = session2.createCriteria(TUser.class);
criteria2.add(Expression.eq("name","Erica"));
List userList = criteria.list();
List userList2 = criteria2.list();TUser user =(TUser)userList.get(0);
TUser user2 =(TUser)userList2.get(0);
Transaction tx = session.beginTransaction();
Transaction tx2 = session2.beginTransaction();
user2.setUserType(99);
tx2.commit();
user.setUserType(1);
tx.commit();
執行以上代碼,代碼將在 tx.commit() 處拋出 StaleObjectStateException  
常,並指出版本檢查失敗,當前事務正在試圖提交一個過時數據。經過捕捉這個異常,我 
們就能夠在樂觀鎖校驗失敗時進行相應處理 併發

相關文章
相關標籤/搜索