Java開發工程師(Web方向) - 03.數據庫開發 - 第4章.事務
第4章--事務
事務原理與開發
事務Transaction:
什麽是事務?
事務是並發控制的基本單位,指作為單個邏輯工作單元執行的一系列操作,且邏輯工作單元需滿足ACID特性。
i.e. 銀行轉賬:開始交易;張三賬戶扣除100元;李四賬戶增加100元;結束交易。
事務的特性:ACID
原子性 Atomicity:整個交易必須作為一個整體來執行。(要麽全部執行,要麽全部不執行)
一致性 Consistency:整個交易總體資金不變
隔離性 Isolation:
case1: 若張三給李四轉賬過程中,趙五給張三轉賬了200元。兩個交易並發執行。
T1 T2
讀取張三余額100;
讀取張三余額100;
給李四轉賬100,
更新張三余額為0;
交易結束 趙五轉入200,
更新張三余額為300
交易結束
case2: 臟讀:張三給別人轉賬100之後張三存錢200,存錢後轉賬由於系統原因失敗回滾。
讀取一個事務未提交的更新
T1 T2
讀取張三余額100
(轉賬) 更新張三余額0
讀取張三余額0
T1 Rollback() (存錢) 更新張三余額200
T2結束(張三賬戶余額為200)
case3: 不可重復讀:同一個事務,兩次讀取同一數值的結果不同,成為不可重復讀。
T1張三讀取自己余額為100;T2讀取張三余額100;T2存錢更新為300;T1張三讀取余額為300。T1中兩次讀取張三余額即為不可重復讀。
case4: 幻讀:兩次讀取的結果包含的行記錄不一樣。
T1讀取所有用戶(張三、李四);T2新增用戶趙五;T1讀取所有用戶(3個);T1/T2結束。T1中兩次讀取的結果中行記錄數不同,稱為幻讀。
需要避免上述cases的產生
隔離性:交易之間相互隔離,在一個交易完成之前,不能受到其他交易的影響
持久性 Durability:整個交易過程一旦結束,無論出現任何情況,交易都應該是永久生效的
使用JDBC進行事務控制:
Connection類中
.setAutoCommit():開啟事務(若為false,則該Connection對象後續的sql都將作為事務來處理;若為true,則該Connection對象後續的所有sql都將作為單獨的語句執行(默認為true))
.commit():事務被提交,即事務生效並結束
.rollback():回滾,回退到事務開始之前的狀態
i.e.
ALTER TABLE user ADD Account int; UPDATE User SET Account = 100 WHERE id = 1; UPDATE User SET Account = 0 WHERE id > 1;
實現ZhangSi(1)給LiSan(2)轉賬的過程:
(非事務:)
public static void TransferNonTransaction() { Connection conn = null; PreparedStatement ptmt = null; try { conn = DriverManager.getConnection(DB_URL, USER_NAME, PASSWORD); String sql = "UPDATE User SET Account = ? WHERE userName = ? AND id = ?;"; // transfer 100 from ZhangSi(1) to LiSan(2) ptmt = conn.prepareStatement(sql); ptmt.setInt(1, 0); ptmt.setString(2, "ZhangSi"); ptmt.setInt(3, 1); ptmt.execute(); ptmt.setInt(1, 100); ptmt.setString(2, "LiSan"); ptmt.setInt(3, 2); ptmt.execute(); } catch (SQLException e) { e.printStackTrace(); } finally { try { if (conn != null) conn.close(); if (ptmt != null) ptmt.close(); } catch (SQLException e) { e.printStackTrace(); } } }
執行完第一個ptmt.execute()後,數據庫中ZhangSi的Account=0, LiSan的Account=0;
出現了一個中間狀態,對於整個業務邏輯的實現是不可接受的。如果此時程序崩潰了將不可挽回。
(事務:)
public static void TransferByTransaction() { Connection conn = null; PreparedStatement ptmt = null; try { conn = DriverManager.getConnection(DB_URL, USER_NAME, PASSWORD); // Using Transaction mechanism conn.setAutoCommit(false); String sql = "UPDATE User SET Account = ? WHERE userName = ? AND id = ?;"; ptmt = conn.prepareStatement(sql); ptmt.setInt(1, 0); ptmt.setString(2, "ZhangSi"); ptmt.setInt(3, 1); ptmt.execute(); ptmt.setInt(1, 100); ptmt.setString(2, "LiSan"); ptmt.setInt(3, 2); ptmt.execute(); // Commit the transaction conn.commit(); } catch (SQLException e) { // if something wrong happens, rolling back if(conn != null) { try { conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } } e.printStackTrace(); } finally { try { if (conn != null) conn.close(); if (ptmt != null) ptmt.close(); } catch (SQLException e) { e.printStackTrace(); } } }
若在第一個ptmt.execute()時斷點,並查詢數據庫,結果為事務執行之前的狀態,並不是中間狀態。
直到conn.commit()方法執行完畢,事務中的所有操作在數據庫中才有效。
Connection類中的檢查點功能:
.setSavePoint():在執行過程中創建保存點,以便rollback()可以回滾到該保存點
.rollback(SavePoint savePoint):回滾到某個檢查點
i.e.
public static void rollbackTest() { Connection conn = null; PreparedStatement ptmt = null; // save point Savepoint sp = null; try { conn = DriverManager.getConnection(DB_URL, USER_NAME, PASSWORD); conn.setAutoCommit(false); String sql = "UPDATE User SET Account = ? WHERE userName = ? AND id = ?;"; ptmt = conn.prepareStatement(sql); ptmt.setInt(1, 0); ptmt.setString(2, "ZhangSi"); ptmt.setInt(3, 1); ptmt.execute(); // create a save point sp = conn.setSavepoint(); ptmt.setInt(1, 100); ptmt.setString(2, "LiSan"); ptmt.setInt(3, 2); ptmt.execute(); // throw an exception manually for the purpose of testing throw new SQLException(); } catch (SQLException e) { // if something wrong happens, rolling back to the save point created before // and then transfer the money to Guoyi(3) if(conn != null) { try { conn.rollback(sp); System.out.println("Transfer from ZhangSi(1) to LiSan(2) failed;\n" + "Transfer to GuoYi(3) instead"); // other operations ptmt.setInt(1, 100); ptmt.setString(2, "GuoYi"); ptmt.setInt(3, 3); ptmt.executeQuery(); conn.commit(); } catch (SQLException e1) { e1.printStackTrace(); } } e.printStackTrace(); } finally { try { if (conn != null) conn.close(); if (ptmt != null) ptmt.close(); } catch (SQLException e) { e.printStackTrace(); } } }
事務的隔離級別:4個級別
讀未提交(read uncommited):可能導致臟讀
讀提交(read commited):不可能臟讀,但是會出現不可重復讀
重復讀(repeatable read):不會出現不可重復讀,但是會出現幻讀
串行化(serializable):最高隔離級別,不會出現幻讀,但嚴格的並發控制、串行執行導致數據庫性能差
N.B. 1. 事務隔離級別越高,數據庫性能越差,但對於開發者而言編程難度越低。
2. MySQL默認事務隔離級別為重復讀 repeatable read
JDBC設置隔離級別:
Connection對象中,
.getTransactionIsolation();
.setTransactionIsolation();
死鎖分析與解決
Java開發工程師(Web方向) - 03.數據庫開發 - 第4章.事務