第一節:事務的並發處理ACID
Atomicity 原子性
Consistency 一致性
Isolation 隔離性
Durability 持久性
第二節:事務並發可能出現的問題
2.1第一類丟失更新(Lost Update)
說明:事務B的更新丟失。撤銷一個事務影響到另外一個事務
時間 |
取款事務A |
存款事務B |
T1 |
開始事務 |
|
T2 |
|
開始事務 |
T3 |
查詢賬戶余額1000元 |
|
T4 |
|
查詢賬戶余額1000元 |
T5 |
|
匯入100元把余額改成1100元 |
T5 |
|
提交事務 |
T7 |
取出100把余額變成900 |
|
T8 |
撤銷事務 |
|
T9 |
余額恢復1000元(丟失更新) |
|
2.2臟讀(ditrty read)
說明:讀到了別的事務還提交的事務,此時B事務還未提交,中間的事務可能回滾,所以讀到數據可能不對。
時間 |
取款事務A |
存款事務B |
T1 |
開始事務 |
|
T2 |
|
開始事務 |
T3 |
|
|
T4 |
|
查詢賬戶余額1000元 |
T5 |
|
匯入100元把余額改成1100元 |
T5 |
查詢賬戶余額1100元 (讀取的臟數據) |
|
T7 |
|
回滾 |
T8 |
取款1100元 |
|
T9 |
提交事務失敗 |
|
2.3不可重復讀(non-repeatable read)
當前事務已經讀取的數據記錄,被其他事務修改或刪除
說明:同一個事務中讀取兩遍的值不一致,此時無法確定按哪個值來處理。
時間 |
取款事務A |
存款事務B |
T1 |
開始事務 |
|
T2 |
查詢賬戶余額1000元 |
|
T3 |
|
開始事務 |
T4 |
|
匯入100元賬戶余額改為1100元 |
T5 |
|
提交事務 |
T5 |
查詢賬戶余額1100元 |
|
T7 |
|
|
T8 |
提交事務 |
|
2.4第二類丟失更新(second lost update)
說明:不可重復讀的特殊情況,本來A事務在提交之前應該再次讀一遍數據,讀取的數據還是和第一次是不一致的。
時間 |
取款事務A |
存款事務B |
T1 |
|
開始事務 |
T2 |
開始事務 |
|
T3 |
|
查詢賬戶余額1000元 |
T4 |
查詢賬戶余額1000元 |
|
T5 |
|
取款100元把余額改成900元 |
T5 |
|
提交事務 |
T7 |
匯入100元 |
|
T8 |
提交事務 |
|
T9 |
余額改成1100元 |
|
2.5幻讀(phantom read)
說明:此時兩次機查詢的數據不一致,同不可重復讀,兩次查詢出來的數據不一致。幻讀為插入和刪除的操作,以上為更新的操作。
其他事務插入了新的數據,當前事務以相同的查詢條件,在那個事務插入數據之前和之后查詢數據,得到的數據記錄的條數不一樣。
時間 |
查詢學生事務A |
查詢學生事務B |
T1 |
開始事務 |
|
T2 |
|
開始事務 |
T3 |
查詢學生為10人 |
|
T4 |
|
插入1個學校 |
T5 |
查詢學生為11人 |
|
T5 |
|
提交事務 |
T7 |
提交事務 |
|
第三節:JDBC事務隔離級別
(1):TRNSACTION_NONE:不支持事務
(2):TRNSACTION_READ_COMMITTED:防止臟讀和幻讀,但是不能限制不可重復讀,因為不可重復讀是讀取已經提交的事務,幻讀類似
(3):TRNSACTION_READ_UNCOMMITTED:允許臟讀,幻讀,不可重復讀
(4):TRNSACTION_REPEATABLE_READ(可重復讀):MYSQL默認設置級別,即把這條數據讀取出來以后加把鎖,其他事務不能更新這條數據的值,等我這個事務處理完成后其他事務才能對該條數據做更新,select xxx rom xxxtable where xxx for update,數據庫為其讀取的這條記錄加把鎖,這條事務沒提交之前,其他事務就不能更新這條記錄
(5):TRNSACTION_SERIALIZABLE:無論多少事務來都不並發,一個一個執行
第四節:悲觀鎖和樂觀鎖
4.1悲觀鎖和樂觀鎖說明
利用悲觀鎖和樂觀鎖解決不可重復讀(non-repeatable read)問題設成READ_COMMITTED通過hibernate實現。
悲觀鎖:通過數據庫加鎖(for update)
樂觀鎖:1:事務取出數據庫中一條記錄的時候為其設置一個version號
2:如果有事務更新了這條數據version版本號自動加一
3:事務把數據更改后檢查版本號是否是剛取出來的那個version,如果不一樣事務 回滾,否則會沖掉人家修改后的數據。
4.2 Hiernate悲觀鎖和樂觀鎖實例
Session session1 = sf.openSession();
Session session2 = sf.openSession();
session1.beginTransaction();
Account a1 = (Account )session1.load(Account .class,1); //把數據庫記錄load出來
session2.beginTransaction();
Account a2 = (Account )session2.load(Account .class,1);
a1.setBalance(100);
a2.setBalance(300);
session1.getTransaction.commit();
session2.getTransaction.commit();