複製鏈接
請複製以下鏈接發送給好友

樂觀鎖

鎖定
樂觀鎖機制採取了更加寬鬆的加鎖機制。相對悲觀鎖而言,樂觀鎖更傾向於開發運用。
中文名
樂觀鎖
外文名
Optimistic Locking
介    紹
記錄機制
應    用
金融行業

樂觀鎖樂觀鎖介紹

樂觀鎖( Optimistic Locking ) 相對悲觀鎖而言,樂觀鎖機制採取了更加寬鬆的加鎖機制。悲觀鎖大多數情況下依靠數據庫的鎖機制實現,以保證操作最大程度的獨佔性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。而樂觀鎖機制在一定程度上解決了這個問題。樂觀鎖,大多是基於數據版本( Version )記錄機制實現。何謂數據版本?即為數據增加一個版本標識,在基於數據庫表的版本解決方案中,一般是通過為數據庫表增加一個 “version” 字段來實現。讀取出數據時,將此版本號一同讀出,之後更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據版本號等於數據庫表當前版本號,則予以更新,否則認為是過期數據。

樂觀鎖示例

如一個金融系統,當某個操作員讀取用户的數據,並在讀出的用户數據的基礎上進行修改時(如更改用户帳户餘額),如果採用悲觀鎖機制,也就意味着整個操作過 程中(從操作員讀出數據、開始修改直至提交修改結果的全過程,甚至還包括操作 員中途去煮咖啡的時間),數據庫記錄始終處於加鎖狀態,可以想見,如果面對幾百上千個併發,這樣的情況將導致怎樣的後果。
樂觀鎖機制在一定程度上解決了這個問題。樂觀鎖,大多是基於數據版本 ( version )記錄機制實現。何謂數據版本?即為數據增加一個版本標識,在基於數據庫表的版本解決方案中,一般是通過為數據庫表增加一個 “version” 字段來實現。
讀取出數據時,將此版本號一同讀出,之後更新時,對此版本號加一。同時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據版本號等於數據庫表當前版本號,則予以更新,否則認為是過期數據。
對於上面修改用户帳户信息的例子而言,假設數據庫中帳户信息表中有一個 version 字段,當前值為 1 ;而當前帳户餘額字段( balance )為 $100 。
1 操作員 A 此時將其讀出( version=1 ),並從其帳户餘額中扣除 $50( $100-$50 )。
2 在操作員 A 操作的過程中,操作員B 也讀入此用户信息( version=1 ),並從其帳户餘額中扣除 $20 ( $100-$20 )。
3 操作員 A 完成了修改工作,將 version=1 的數據連同帳户扣除後餘額( balance=$50 ),提交至數據庫更新,此時由於提交數據版本等於數據庫記錄當前版本,數據被更新,同時數據庫記錄 version 更新為 2(set version=version+1 where version=1) 。
4 操作員 B 完成了數據錄入操作,也將 version=1 的數據試圖向數據庫提交( balance=$80 ),但此時比對數據庫記錄版本時發現,操作員 B 提交的數據版本號為 1 ,數據庫記錄當前版本也為 2 ,不滿足 “ 提交版本必須等於記錄當前版本才能執行更新 “ 的樂觀鎖策略,因此,操作員 B 的提交被駁回。
這樣,就避免了操作員 B 用基於 version=1 的舊數據修改的結果覆蓋操作員A 的操作結果的可能。

樂觀鎖優點

從上面的例子可以看出,樂觀鎖機制避免了長事務中的數據庫加鎖開銷(操作員 A和操作員 B 操作過程中,都沒有對數據庫數據加鎖),大大提升了大併發量下的系統整體性能表現。

樂觀鎖缺點

需要注意的是,樂觀鎖機制往往基於系統中的數據存儲邏輯,因此也具備一定的侷限性,如在上例中,由於樂觀鎖機制是在我們的系統中實現,來自外部系統的用户餘額更新操作不受我們系統的控制,因此可能會造成髒數據被更新到數據庫中。在系統設計階段,我們應該充分考慮到這些情況出現的可能性,並進行相應調整(如將樂觀鎖策略在數據庫存儲過程中實現,對外只開放基於此存儲過程的數據更新途徑,而不是將數據庫表直接對外公開)。

樂觀鎖實現

Hibernate 在其數據訪問引擎中內置了樂觀鎖實現。如果不用考慮外部系統對數據庫的更新操作,利用 Hibernate 提供的透明化樂觀鎖實現,將大大提升我們的生產力。
Hibernate 中可以通過 class 描述符的 optimistic-lock 屬性結合 version
描述符指定。
現在,我們為之前示例中的 TUser 加上樂觀鎖機制。

樂觀鎖添加屬性

首先為 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 屬性有如下可選取值:
&Oslash; none
無樂觀鎖
&Oslash; version
通過版本機制實現樂觀鎖
&Oslash; dirty
通過檢查發生變動過的屬性實現樂觀鎖
&Oslash; all
通過檢查所有屬性實現樂觀鎖
其中通過 version 實現的樂觀鎖機制是 Hibernate 官方推薦的樂觀鎖實現,同時也
是 Hibernate 中,目前唯一在數據對象脱離 Session 發生修改的情況下依然有效的鎖機
制。因此,一般情況下,我們都選擇 version 方式作為 Hibernate 樂觀鎖實現機制。

樂觀鎖添加描述符

添加一個 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">
</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 異常,並指出版本檢查失敗,當前事務正在試圖提交一個過期數據。通過捕捉這個異常,我們就可以在樂觀鎖校驗失敗時進行相應處理。