目錄
- 一:事務的定義及作用
- 二:事務的四個特性(ACID)
- 三:JDBC事務
- 四:隔離級別
- 五、總結
一:事務的定義及作用
事務(Transaction),一般是指要做的或所做的事情。在計算機術語中是指訪問并可能更新數據庫中各種數據項的一個程序執行單元(unit)。事務一般由事務開始(begin transaction)和事務結束(end transaction)之間執行的全體操作組成。那么,在平時的應用中,為什么要使用事務呢?這里筆者想通過一個簡單的例子來說明事務的重要性。支付寶是生活中經常使用的轉賬手段,假如賬戶A要通過支付寶將自己賬戶上的100元轉到B賬戶,那么對于A賬戶來說余額就要減去100元,然后在B賬戶里增加100元,此時算是轉賬成功。假如就在A賬戶減去100元時,不巧出現了網絡故障,B賬戶里還未來得及增加100元,那么整個轉賬業務就會出現問題。所以,為了保證業務的完整性,就需要通過事務來控制,將A減少100元和B增加100元放入同一個事務中,則該事務要么執行成功,要么執行失敗后全部撤銷,以此來保證數據的安全。
二:事務的四個特性(ACID)
事務的四個特性包括原子性(atomicity),一致性(consistency),隔離性(isolation)和持久性(durability)。筆者將通過上文的例子來幫助讀者理解事務的這四個特性。1.原子性:事務是數據庫的邏輯工作單位,不可再分。當我們把“A減少100和B增加100”加入一個事務時,這個將會成為數據庫工作的最小單位,對于A和B數據的修改,要么全部成功,要么全部失敗,不會出現某一個成功另一個失敗的情況。2.一致性:在事務處理執行前后,數據庫是一致的(數據庫數據完整性約束)。假設A原來有100元,B原來有0元,那么最初A賬戶和B賬戶一共有100元;當事務結束時,即轉賬成功后,最終A賬戶和B賬戶一共有100元,與最初的值保持不變,是數據達到一致。3.隔離性:每個事務都是獨立的,一個事務的執行不能被其他事務所影響。例如A給B轉賬100元,C給A轉賬100元,那么“A減少100元,B增加100元”與“C減少100元,A增加100元”屬于兩個不同的事務,并且兩個事務相對獨立。4.持久性:事務處理的結果能夠被永久保存在數據庫中。
三:JDBC事務
在JDBC中處理事務,都是通過Connection完成的。同一事務中所有的操作,都在使用同一個Connection對象。JDBC事務默認是開啟的,并且是默認提交。下面是事務在JAVA中的最基本操作: connection.setAutoCommit(boolean);//設置是否為自動提交事務,如果true(默認值為true)表示自動提交,如果設置為false,需要手動提交事務。 connection.commit();//提交事務。 connection.rollback();//回滾事務。下面通過示例代碼展示一下:
package cn.itcast.jdbc; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Savepoint; import java.sql.Statement; /** * 事務測試 */ public class test { public static void main(String[] args) throws SQLException { testTransaction(); } static void testTransaction() throws SQLException { Connection conn = null; Statement st = null; ResultSet rs = null; Savepoint sp = null; try { conn = JdbcUtils.getConnection(); //將事務設置成手動提交 conn.setAutoCommit(false); st = conn.createStatement(); //id為1的人的Money減100 String sql = "update user set money=money-100 where id=1"; st.executeUpdate(sql); //設置回滾點(savepoint) sp = conn.setSavepoint(); //id為2的人的Money減100 sql = "update user set money=money-100 where id=2"; st.executeUpdate(sql); sql = "select money from user where id=2"; rs = st.executeQuery(sql); float money = 0.0f; if (rs.next()) { money = rs.getFloat("money"); } if (money > 300){ throw new RuntimeException("已經超過最大值!"); } //id為2的人的Money加100 sql = "update user set money=money+100 where id=2"; st.executeUpdate(sql); //提交事務 conn.commit(); } catch (RuntimeException e) { if (conn != null && sp != null) { //回滾事務,注意里面的參數sp即為我們上面設置的savePoint,如果回滾的話只能回滾到savePoint以下的部分 //上面的部分不會得到回滾 conn.rollback(sp); conn.commit(); } throw e; } catch (SQLException e) { if (conn != null) conn.rollback(); throw e; } finally { //釋放資源 JdbcUtils.free(rs, st, conn); } } }
四:隔離級別
為了應對多線程并發讀取數據時出現的問題,事務有了“隔離級別”特性,多線程并發讀取數據一般會引發如下三個問題:
- 臟讀(dirtyreads):指一個事務讀取了另外一個事務未提交的數據。假設A事務讀取B事務尚未提交的數據,此時如果B事務發生錯誤并執行回滾操作,那么A事務讀取到的數據就是臟數據。 2.不可重復讀(non-repeatablereads):一個事務重新讀取前面讀取過的數據, 發現該數據已經被另一個已提交的事務修改過。假如事務A在執行讀取操作,由整個事務A比較大,前后讀取同一條數據需要經歷很長的時間 。而在事務A第一次讀取數據,比如此時讀取了小明的年齡為20歲,事務B執行更改操作,將小明的年齡更改為30歲,此時事務A第二次讀取到小明的年齡時,發現其年齡是30歲,和之前的數據不一樣了,也就是數據不重復了,系統不可以讀取到重復的數據,稱為不可重復讀。3.幻讀(phantomread):是指在一個事務內讀取到了別的事務插入的數據,導致前后讀取不一致。假設事務A在執行讀取操作,需要兩次統計數據的總量,前一次查詢數據總量后,此時事務B執行了新增數據的操作并提交后,這個時候事務A讀取的數據總量和之前統計的不一樣,就像產生了幻覺一樣,平白無故的多了幾條數據,稱為成為幻讀。會發現,幻讀和不可重復讀是十分相似的,以致于很多人很難分辨它們,你只需要知道,它們最大的區別是:不可重復讀讀取到的是更新(update)數據,而幻讀讀取到的是插入(insert)數據。為了處理上面的讀數據問題,java事務提供了4種隔離級別。
- 可串行化(Serializable):可避免臟讀、不可重復讀、虛讀情況的發生。
- 可重復讀(Repeatableread):可避免臟讀、不可重復讀情況的發生。不可以避免虛讀。3.讀已提交(Readcommitted):可避免臟讀情況發生。4.讀未提交(Read uncommitted):最低級別,以上情況均無法保證。
1表示有,0表示無
隔離級別臟讀不可重復讀幻讀讀未提交(Read uncommitted)111讀已提交(Readcommitted)011可重復讀(Repeatableread)001可串行化(Serializable)000
隔離級別由高到低排列:可串行化>可重復讀>讀已提交>讀未提交。通常情況下,數據庫都有自己的默認隔離級別,我們使用spring框架可以指定隔離級別,但是如果指定了數據庫不支持的隔離級別,數據庫就會使用自己默認的。在Oracle數據庫中,默認隔離級別是Read committed,而另一個常用數據庫MySQL中,默認隔離級別是Repeatable read。下面,我們用mysql的例子說明各個隔離級別的情況:開啟兩個命令行客戶端分別為A,B;不斷改變A的隔離級別,在B端修改數據。實際步驟同序號。
1.讀未提交(最低的隔離級別):


- 讀已提交:



- 可重復讀



值得一提的是,如果在客戶端A中接著執行update num= num + 1 where id = 1,num沒有變成1+1=2,而是步驟(2)中更新過后的num=10來算的,所以是10 + 1 = 11,數據的一致性倒是沒有被破壞。可重復讀的隔離級別下使用了MVCC機制,select操作不會更新版本號,是快照讀(歷史版本);insert、update和delete會更新版本號,是當前讀(當前版本)。
4.串行化


mysql中事務隔離級別為serializable時會鎖表,若一個事務來查詢同一份數據就必須等待,直到前一個事務完成并解除鎖定為止,因此不會出現幻讀的情況,這種隔離級別并發性極低,開發中很少會用到。
五、總結
事務控制是構建J2EE應用不可缺少的一部分,合理選擇應用何種事務對整個應用系統來說至關重要。一般說來,在單個JDBC 連接連接的情況下可以選擇JDBC事務,在跨多個連接或者數據庫情況下,需要選擇使用JTA事務,如果用到了EJB,則可以考慮使用EJB容器事務,有興趣的朋友可以關注一下。對于隔離級別來說,讀未提交、讀已提交和可重復讀這三種隔離級別隔離的是行數據,他們的不同只是對應讀、寫之間的鎖定關系不同而已,讀未提交,事務進行寫操作時并沒有鎖定禁止讀的動作;讀已提交在進行事務在進行寫操作時鎖定了行數據,禁止在寫期間讀數據;而可重復讀則是在讀期間禁止數據的寫;串行化即鎖定整個表的讀寫。希望讀者能夠合理選擇并使用它們。