2013年7月18日 星期四

Transaction 交易管理

由於工作上剛好遇到 交易鎖定問題,一直懵懵懂懂,特地花了一些時間了解整個來龍去脈,並整理成筆記。

此文章測試專案,使用spring3 + hibernate4 與 Mysql 來作測試


1.面臨問題

報名系統的開課人數10人,
假設以報名9人,剩下名額1人
同時間有數人一起報名,
如何避免競賽情況(Race Condition)!?

預期解決方案:
1.隔離層
2.樂觀鎖定
3.悲觀鎖定
4.Synchronized

2. 交易管理的概念

交易(Transaction)是一個單元工作(unit of work),這個單元工作包括了數個步驟來完成,這數個步驟必須全部執行成功,交易才算成功,只有當中有一個失敗,則整個交易宣告失敗。

交易的四個基本要求是

A.原子性(Atomicity)
一個交易是一個單元工作,當中可能包括數個步驟,這些步驟正常全部執行成功,若有一個失敗,則整個交易宣告失敗,交易中其它步驟也必須撤消曾經執行過的動作,回到交易前的狀態。

B.一致性(Consistency)
指交易所作用的資料集合在交易前後必須一致,也就是這組資料集合若交易成功,則整個資料集合都必須是交易操作後的狀態,若交易失敗,所整個資料集合必須與開始交易之前一樣完全沒有變更,不能發生整個資料集合,部份有變更,而部份沒變更的狀態。

C.隔離行為(Isolation behavior)
在多人使用的環境下,每個使用者可能進行自己的交易,交易與交易之間,必須互不干擾,使用者不會意識到別的使用者正在進行交易,就好像只有自己在進行操作一樣。 

D.持續性(Durability)
交易一旦成功,則所有的變更必須保存下來,即使系統掛了,交易的結果也不能遺失。


3.Spring 的 交易管理

Spring 使用 切面導向程式設計(AOP)來完成交易管理,宣告式交易是以方法為單位。
主要可以設定4個參數
A.傳播行為
B.隔離層級
C.唯獨提示
D.交易超過時間

4.傳播行為


§Propagation.REQUIRED
支援現在的交易,如果沒有 就建立一個新的交易。
§Propagation.REQUIRES_NEW
不管是否存在交易,都創建一個新的交易,原來的暫停,新的執行完畢,繼續執行原來的交易。(常用於記錄LOG)
§Propagation.SUPPORTS
調用的類別方法若有使用交易,就加入交易,若沒有就不使用交易。
§Propagation.NOT_SUPPORTED
容器不為這個方法開啟交易。(若再加入交易會丟出例外)
§Propagation.MANDATORY
方法必須在一個現存的交易中進行,否則丟出例外。
§Propagation.NEVER
指不應在交易中進行,如果有的話就丟出例外。
§Propagation.NESTED
在一個巢狀的交易中進行,如果不是的話,則同Propagation.REQUIRED

5.隔離層級


§Isolation = Isolation.DEFAULT
          使用底層資料庫預設的隔離層級。
§Isolation=Isolation.READ_UNCOMMITTED
允許交易讀取其它並行交易還沒送出的資料,會發生Dirty read
  Non Repeatable phantom read lost update等問題。
§Isolation = Isolation.READ_COMMITTED
允許交易讀取其它並行交易已送出的資料欄位,可防止 Dirty read 、。
§Isolation = Isolation.REPEATABLE_READ
要求多次讀取的資料必須相同,除非交易本身更新資料,會發生 phantom read問題。
§Isolation = Isolation.SERIALIZABLE
完整的隔離層級,可以防止Dirty read Non Repeatable phantom read等問題,但是會鎖定資料表格,因而有效能問題。
§隔離交易的基本方式是鎖定資料庫,但完全的鎖定資料庫實務上並不會這麼作,因為將導致嚴重的效能問題,因此實務上會根據資料讀寫更新的頻繁性,設定不同的交易隔離層級。
§MYSQL: 預設为REPEATABLE_READ

6.資料庫沒有鎖定,可能發生的問題


§髒讀(dirty read)
兩個交易同時進行,其中一個交易更新資料,另一個交易讀取了
COMMIT的資料,就有可能發生髒讀問題。例如:
1.交易A  更新欄位1
2.交易B  讀取欄位1
3.交易A ROLLBACK
4.交易B  COMMIT
在以上的情況下,交易B讀取的是不正確的資料

§不一致(Non Repeatable)
某個交易兩次讀取同一欄位的資料並不一致,例如,如果交易A
在交易B前後進行資料的讀取,則會得到不同的結果。
1.交易A讀取欄位1
2.交易B更新欄位1
3.交易B COMMIT
4.交易A讀取欄位1
在以上的情況,交易A讀取兩次欄位1,但卻得到不同的結果。

§幻讀(phantom read)
如果交易A進行兩次查詢,在兩次查詢之中有個交易B插入一筆新
資料或刪除一筆新資料,第二次查詢時得到的資料多了第一次
查詢時所沒有的筆數,或者少了一筆。
1.交易A進行查詢得到五筆資料
2.交易B插入一筆資料
3.交易B COMMIT
4.交易A進行查詢得到六筆資料

7.丟失更新
例如:
用戶A讀取學號為107的學生(學號=107,姓名=“小明”,年齡= 28
用戶B讀取學號為107的學生(學號=107,姓名=“小明”,年齡= 28
用戶A把姓名更改(學號=107,姓名=“王小明”,年齡= 28
用戶B把年齡更改為33(學號=107,姓名=“小明”,年齡=33
用戶A提交(學號=107,姓名=“王小明”,年齡= 28
用戶B提交(學號=107,姓名=“小明”,年齡= 33
用戶A對學生姓名的更新丟失了。
單獨使用隔離層會產生Deadlock錯誤,可搭配樂觀鎖、悲觀鎖、Synchronized ,來防止丟失更新錯誤

8.唯獨提示

§readOnly = false

§如果交易只進行讀取的動作,則可以利用底層資料庫在唯讀操 作時的一些最佳化動作,由於這個動作利用到資料庫在唯讀的交易操作最佳化,因而必須在交易中才有效,也就是要搭配下列傳播行為 :
A.PROPAGATION_REQUIRED
B.PROPAGATION_REQUIRES_NEW
C.PROPAGATION_NESTED

9.交易超過時間

§timeout=30

§有的交易操作可能延續一段很長的時間,交易本身可能關聯到資料表格的鎖定,因而長時間的交易操作會有效能上的問題,對於過長的交易操作,要考慮回滾Roll back)交易並要求重新操作,而不是無限時的等待交易完成,必須搭配下列傳播行為 :

A.PROPAGATION_REQUIRED
B.PROPAGATION_REQUIRES_NEW
C.PROPAGATION_NESTED

10.解決方案1: 設定 Isolation

@Transactional(
readOnly = false,
propagation = Propagation.REQUIRED,
 isolation=Isolation.DEFAULT
)

11.Hibernate 樂觀鎖定

§樂觀鎖定Optimistic locking樂觀的認為資料很少發生同時存取的問題,通常在資料庫層級上設為read-commited隔離層級,並實行樂觀鎖定。

§樂觀鎖定Hibernate所推薦的方式,在資料庫中加入一個version欄位記錄,在讀取資料時 連同版本號一同讀取,並在更新資料時比對版本號與資料庫中的版本號,如果等於資料庫中的版本號則予以更新,並遞增版本號,如果小於資料庫中的版本號就丟出 StaleObjectStateException(JPA)例外。

§根據 底層 ORM 不同,會丟出不同例外

:Hibernate  HibernateOptimisticLockingFailureException


12.解決方案2: Value Object(VO) 加入@Version

@Version
@Column(name = "VERSION")
private Integer version;
Get ()
Set ()

13.解決方案2:樂觀鎖使用






14.Hibernate 悲觀鎖定

§悲觀鎖定Pessimistic Locking)一如其名稱所示,悲觀的認定每次資料存取時,其它的客戶端也會存取同一筆資料,因此對相關的資料進行鎖定,直到自己操作完成後解除鎖定。 
§:API:LockMode

15.setLockMode()

簡略介紹
§NONE 
  不使用鎖定。
§OPTIMISTIC_FORCE_INCREMENT 
  當使用版本號進行 樂 觀鎖定 (Optimistic Locking 時,可強迫指定的物件進行版本號遞增)
§PESSIMISTIC_WRITE
  交易將立即啟用數據庫鎖定。
§WRITE 
  insertupdate時進行鎖定,Hibernate會在進行資料寫入時自動獲得鎖定。

§如果資料庫不支援所指定的鎖定模式Hibernate會選擇一個合適的鎖定替換,而不是丟出一個例外。

16. 解決方案3: DAO 加入.setLockMode()




PESSIMISTIC_WRITE 將會利用 for update 鎖住資料,等到交易結束才解鎖。


17.解決方案4: Synchronized

將service 被叫用的方法加上 synchronized
再多執行緒下,可以確保交易順利進行


測試結果



未使用 synchronized 結果

18.最後建議

§根據不同情況,可以使用不同的解決方法,以下提供 幾種方案
1.Transaction 搭配 樂觀鎖定
優點:適用於資料發生衝突機會低的情況,可以搭配錯誤訊息: 系統忙碌中,請稍後再用 。
缺點:多個JVM Cluster下無法使用,DB 資料表 多系統讀寫無法使用。

2.Transaction 搭配 悲觀鎖定
優點:適用於資料發生衝突機會高的情況,和資料需要正確性。
缺點:犧牲資料庫速度,根據不同資料庫鎖定方式也不同分成row lock(消耗資源高) 、table lock (消耗資源低) ,(MySQL 採用 row lock)。

3.Transaction 搭配 Synchronized
優點:從程式面多執行緒來作處理,強制Lock物件,來確保交易正確性。
缺點:犧牲整體程式速度,且在多個JVM Cluster下無法使用。

沒有留言:

張貼留言