Hibernate事務與併發問題處理(樂觀鎖與悲觀鎖) [轉]

Hibernate事務與併發問題處理(樂觀鎖與悲觀鎖) sql

1、數據庫事務的定義 數據庫

  數據庫事務(Database Transaction) ,是指做爲單個邏輯工做單元執行的一系列操做。事務處理能夠確保除非事務性單元內的全部操做都成功完成,不然不會永久更新面向數據的資源。經過將一組相關操做組合爲一個要麼所有成功要麼所有失敗的單元,能夠簡化錯誤恢復並使應用程序更加可靠。一個邏輯工做單元要成爲事務,必須知足所謂的ACID(原子性、一致性、隔離性和持久性)屬性。 併發

  1. 原子性(atomic),事務必須是原子工做單元;對於其數據修改,要麼全都執行,要麼全都不執行 app

  2. 一致性(consistent),事務在完成時,必須使全部的數據都保持一致狀態。 性能

  3. 隔離性(insulation),由併發事務所做的修改必須與任何其它併發事務所做的修改隔離。 ui

  4. 持久性(Duration),事務完成以後,它對於系統的影響是永久性的。 atom

2、數據庫事務併發可能帶來的問題 spa

  若是沒有鎖定且多個用戶同時訪問一個數據庫,則當他們的事務同時使用相同的數據時可能會發生問題。因爲併發操做帶來的數據不一致性包括:丟失數據修改、讀」髒」數據(髒讀)、不可重複讀、產生幽靈數據: .net

假設數據庫中有以下一張表: hibernate

  1. 第一類丟失更新(lost update): 在徹底未隔離事務的狀況下,兩個事物更新同一條數據資源,某一事物異常終止,回滾形成第一個完成的更新也同時丟失。

  在T1時刻開啓了事務1,T2時刻開啓了事務2,在T3時刻事務1從數據庫中取出了id="402881e535194b8f0135194b91310001"的數據,T4時刻事務2取出了同一條數據,T5時刻事務1將age字段值更新爲30,T6時刻事務2更新age爲35並提交了數據,可是T7事務1回滾了事務age最後的值依然爲20,事務2的更新丟失了,這種狀況就叫作"第一類丟失更新(lost update)"。

  2. 髒讀(dirty read):若是第二個事務查詢到第一個事務還未提交的更新數據,造成髒讀。

  在T1時刻開啓了事務1,T2時刻開啓了事務2,在T3時刻事務1從數據庫中取出了id="402881e535194b8f0135194b91310001"的數據,在T5時刻事務1將age的值更新爲30,可是事務還未提交,T6時刻事務2讀取同一條記錄,得到age的值爲30,可是事務1還未提交,若在T7時刻事務1回滾了事務2的數據就是錯誤的數據(髒數據),這種狀況叫作" 髒讀(dirty read)"。

  3. 虛讀(phantom read):一個事務執行兩次查詢,第二次結果集包含第一次中沒有或者某些行已被刪除,形成兩次結果不一致,只是另外一個事務在這兩次查詢中間插入或者刪除了數據形成的。

  在T1時刻開啓了事務1,T2時刻開啓了事務2,T3時刻事務1從數據庫中查詢全部記錄,記錄總共有一條,T4時刻事務2向數據庫中插入一條記錄,T6時刻事務2提交事務。T7事務1再次查詢數據數據時,記錄變成兩條了。這種狀況是"虛讀(phantom read)"。

  4. 不可重複讀(unrepeated read):一個事務兩次讀取同一行數據,結果獲得不一樣狀態結果,如中間正好另外一個事務更新了該數據,兩次結果相異,不可信任。

  在T1時刻開啓了事務1,T2時刻開啓了事務2,在T3時刻事務1從數據庫中取出了id="402881e535194b8f0135194b91310001"的數據,此時age=20,T4時刻事務2查詢同一條數據,T5事務2更新數據age=30,T6時刻事務2提交事務,T7事務1查詢同一條數據,發現數據與第一次不一致。這種狀況就是"不可重複讀(unrepeated read)"。

  5. 第二類丟失更新(second lost updates):是不可重複讀的特殊狀況,若是兩個事務都讀取同一行,而後兩個都進行寫操做,並提交,第一個事務所作的改變就會丟失。

  在T1時刻開啓了事務1,T2時刻開啓了事務2,T3時刻事務1更新數據age=25,T5時刻事務2更新數據age=30,T6時刻提交事務,T7時刻事務2提交事務,把事務1的更新覆蓋了。這種狀況就是"第二類丟失更新(second lost updates)"。

3、數據庫事務隔離級別

爲了解決數據庫事務併發運行時的各類問題數據庫系統提供四種事務隔離級別:
1. Serializable 串行化
2. Repeatable Read 可重複讀
3. Read Commited 可讀已提交
4. Read Uncommited 可讀未提交

隔離級別與併發性能的關係:

每個隔離級別能夠解決的問題:


4、使用Hibernate設置數據庫隔離級別

在Hibernate的配置文件中能夠顯示的配置數據庫事務隔離級別。每個隔離級別用一個整數表示:

8 - Serializable 串行化
4 - Repeatable Read 可重複讀
2 - Read Commited 可讀已提交
1 - Read Uncommited 可讀未提交

在hibernate.cfg.xml中使用hibernate.connection.isolation參數配置數據庫事務隔離級別。

5、使用悲觀鎖解決事務併發問題

  悲觀鎖,正如其名,它指的是對數據被外界(包括本系統當前的其餘事務,以及來自外部系統的事務處理)修改持保守態度,所以,在整個數據處理過程當中,將數據處於鎖定狀態。悲觀鎖的實現,每每依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,不然,即便在本系統中實現了加鎖機制,也沒法保證外部系統不會修改數據)。

  一個典型的依賴數據庫的悲觀鎖調用:select * from account where name=」Erica」 for update這條 sql 語句鎖定了 account 表中全部符合檢索條件( name=」Erica」 )的記錄。本次事務提交以前(事務提交時會釋放事務過程當中的鎖),外界沒法修改這些記錄。悲觀鎖,也是基於數據庫的鎖機制實現。

在Hibernate使用悲觀鎖十分容易,但實際應用中悲觀鎖是不多被使用的,由於它大大限制了併發性:

圖爲Hibernate3.6的幫助文檔Session文檔的get方法截圖,能夠看到get方法第三個參數"lockMode"或"lockOptions",注意在Hibernate3.6以上的版本中"LockMode"已經不建議使用。方法的第三個參數就是用來設置悲觀鎖的,使用第三個參數以後,咱們每次發送的SQL語句都會加上"for update"用於告訴數據庫鎖定相關數據。

LockMode參數選擇該選項,就會開啓悲觀鎖。

  T1,T2時刻取款事務和轉帳事務分別開啓,T3事務查詢ACCOUNTS表的數據並用悲觀鎖鎖定,T4轉帳事務也要查詢同一條數據,數據庫發現該記錄已經被前一個事務使用悲觀鎖鎖定了,而後讓轉帳事務等待直到取款事務提交。T6時刻取款事務提交,T7時刻轉帳事務獲取數據。

6、使用樂觀鎖解決事務併發問題

  相對悲觀鎖而言,樂觀鎖機制採起了更加寬鬆的加鎖機制。悲觀鎖大多數狀況下依靠數據庫的鎖機制實現,以保證操做最大程度的獨佔性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷每每沒法承受。樂觀鎖機制在必定程度上解決了這個問題。樂觀鎖,大可能是基於數據版本(Version)記錄機制實現。何謂數據版本?即爲數據增長一個版本標識,在基於數據庫表的版本解決方案中,通常是經過爲數據庫表增長一個"version"字段來實現。
  樂觀鎖的工做原理:讀取出數據時,將此版本號一同讀出,以後更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,若是提交的數據版本號大於數據庫表當前版本號,則予以更新,不然認爲是過時數據。

Hibernate爲樂觀鎖提供了3中實現:

1. 基於version

2. 基於timestamp

3. 爲遺留項目添加添加樂觀鎖

配置基於version的樂觀鎖:

複製代碼

<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.suxiaolei.hibernate.pojos.People" table="people"> <id name="id" type="string"> <column name="id"></column> <generator class="uuid"></generator> </id> <!-- version標籤用於指定表示版本號的字段信息 --> <version name="version" column="version" type="integer"></version> <property name="name" column="name" type="string"></property> </class> </hibernate-mapping>

配置基於timestamp的樂觀鎖:


<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.suxiaolei.hibernate.pojos.People" table="people"> <id name="id" type="string"> <column name="id"></column> <generator class="uuid"></generator> </id> <!-- timestamp標籤用於指定表示版本號的字段信息 --> <timestamp name="updateDate" column="updateDate"></timestamp> <property name="name" column="name" type="string"></property> </class> </hibernate-mapping>

遺留項目,因爲各類緣由沒法爲原有的數據庫添加"version"或"timestamp"字段,這時不可使用上面兩種方式配置樂觀鎖,Hibernate爲這種狀況提供了一個"optimisitic-lock"屬性,它位於<class>標籤上:


<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.suxiaolei.hibernate.pojos.People" table="people" optimistic-lock="all"> <id name="id" type="string"> <column name="id"></column> <generator class="uuid"></generator> </id> <property name="name" column="name" type="string"></property> </class> </hibernate-mapping>

將該屬性的值設置爲all,讓該記錄全部的字段都爲版本控制信息。

相關文章
相關標籤/搜索