JDBC事務總結
系列閱讀
1. 事務
- 事務的四大特性:ACID
- mysql中操作事務
- jdbc中操作事務
1.1 事務概述
為了方便演示事務,我們需要建立一個account表:
CREATE TABLE account( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(30), balance NUMERIC(10.2) ); INSERT INTO account(NAME,balance) VALUES('zs', 100000); INSERT INTO account(NAME,balance) VALUES('ls', 100000); INSERT INTO account(NAME,balance) VALUES('ww', 100000); SELECT * FROM account;
1.2 什麼是事務
銀行轉賬!張三轉10000塊到李四的賬戶,這其實需要兩條SQL語句:
- 給張三的賬戶減去10000元
- 給李四的賬戶加上10000元
如果在第一條SQL語句執行成功後,在執行第二條SQL語句之前,程式被中斷了(可能是丟擲了某個異常,也可能是其他什麼原因),那麼李四的賬戶沒有加上10000元,而張三卻減去了10000元。這肯定是不行的!
你現在可能已經知道什麼是事務了吧!事務中的多個操作,要麼完全成功,要麼完全失敗!不可能存在成功一半的情況!也就是說給張三的賬戶減去10000元如果成功了,那麼給李四的賬戶加上10000元的操作也必須是成功的;否則給張三減去10000元,以及給李四加上10000元都是失敗的!
1.3 事務的四大特性(ACID)
事務的四大特性是:
原子性(Atomicity):事務中所有操作是不可再分割的原子單位。事務中所有操作要麼全部執行成功,要麼全部執行失敗
一致性(Consistency):事務執行後,資料庫狀態與其它業務規則保持一致。如轉賬業務,無論事務執行成功與否,參與轉賬的兩個賬號餘額之和應該是不變的
隔離性(Isolation):隔離性是指在併發操作中,不同事務之間應該隔離開來,使每個併發中的事務不會相互干擾
永續性(Durability):一旦事務提交成功,事務中所有的資料操作都必須被持久化到資料庫中,即使提交事務後,資料庫馬上崩潰,在資料庫重啟時,也必須能保證通過某種機制恢復資料
1.4 MySQL中的事務
在預設情況下,MySQL每執行一條SQL語句,都是一個單獨的事務。如果需要在一個事務中包含多條SQL語句,那麼需要開啟事務和結束事務。
- 開啟事務:start transaction
- 結束事務:commit或rollback
在執行SQL語句之前,先執行strat transaction,這就開啟了一個事務(事務的起點),然後可以去執行多條SQL語句,最後要結束事務,commit表示提交,即事務中的多條SQL語句所做出的影響會持久化到資料庫中。或者rollback,表示回滾,即回滾到事務的起點,之前做的所有操作都被撤消了!
下面演示zs給li轉賬10000元的示例:
START TRANSACTION;
UPDATE account SET balance=balance-10000 WHERE id=1;
UPDATE account SET balance=balance+10000 WHERE id=2;
ROLLBACK;
START TRANSACTION;
UPDATE account SET balance=balance-10000 WHERE id=1;
UPDATE account SET balance=balance+10000 WHERE id=2;
COMMIT;
START TRANSACTION;
UPDATE account SET balance=balance-10000 WHERE id=1;
UPDATE account SET balance=balance+10000 WHERE id=2;
quit;
2. JDBC事務
在jdbc中處理事務,都是通過Connection完成的!同一事務中所有的操作,都在使用同一個Connection物件!
2.1、JDBC中的事務
Connection的三個方法與事務相關:
- setAutoCommit(boolean):設定是否為自動提交事務,如果true(預設值就是true)表示自動提交,也就是每條執行的SQL語句都是一個單獨的事務,如果設定false,那麼就相當於開啟了事務了
- commit():提交結束事務
- rollback():回滾結束事務
jdbc處理事務的程式碼格式:
try {
con.setAutoCommit(false);//開啟事務…
….
…
con.commit();//try的最後提交事務
} catch() {
con.rollback();//回滾事務
}
public void transfer(boolean b) {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = JdbcUtils.getConnection();
//手動提交
con.setAutoCommit(false);
String sql = "update account set balance=balance+? where id=?";
pstmt = con.prepareStatement(sql);
//操作
pstmt.setDouble(1, -10000);
pstmt.setInt(2, 1);
pstmt.executeUpdate();
// 在兩個操作中丟擲異常
if(b) {
throw new Exception();
}
pstmt.setDouble(1, 10000);
pstmt.setInt(2, 2);
pstmt.executeUpdate();
//提交事務
con.commit();
} catch(Exception e) {
//回滾事務
if(con != null) {
try {
con.rollback();
} catch(SQLException ex) {}
}
throw new RuntimeException(e);
} finally {
//關閉
JdbcUtils.close(con, pstmt);
}
}
3. 儲存點
儲存點是JDBC3.0的東西!當要求資料庫伺服器支援儲存點方式的回滾。
校驗資料庫伺服器是否支援儲存點!
boolean b = con.getMetaData().supportsSavepoints();
儲存點的作用是允許事務回滾到指定的儲存點位置。在事務中設定好儲存點,然後回滾時可以選擇回滾到指定的儲存點,而不是回滾整個事務!注意,回滾到指定儲存點並沒有結束事務!!!只有回滾了整個事務才算是結束事務了!
Connection類的設定儲存點,以及回滾到指定儲存點方法:
- 設定儲存點:Savepoint setSavepoint()
- 回滾到指定儲存點:void rollback(Savepoint)
/*
* 李四對張三說,如果你給我轉1W,我就給你轉100W。
* ==========================================
*
* 張三給李四轉1W(張三減去1W,李四加上1W)
* 設定儲存點!
* 李四給張三轉100W(李四減去100W,張三加上100W)
* 檢視李四餘額為負數,那麼回滾到儲存點。
* 提交事務
*/
@Test
public void fun() {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = JdbcUtils.getConnection();
//手動提交
con.setAutoCommit(false);
String sql = "update account set balance=balance+? where name=?";
pstmt = con.prepareStatement(sql);
//操作1(張三減去1W)
pstmt.setDouble(1, -10000);
pstmt.setString(2, "zs");
pstmt.executeUpdate();
//操作2(李四加上1W)
pstmt.setDouble(1, 10000);
pstmt.setString(2, "ls");
pstmt.executeUpdate();
// 設定儲存點
Savepoint sp = con.setSavepoint();
//操作3(李四減去100W)
pstmt.setDouble(1, -1000000);
pstmt.setString(2, "ls");
pstmt.executeUpdate();
//操作4(張三加上100W)
pstmt.setDouble(1, 1000000);
pstmt.setString(2, "zs");
pstmt.executeUpdate();
//操作5(檢視李四餘額)
sql = "select balance from account where name=?";
pstmt = con.prepareStatement(sql);
pstmt.setString(1, "ls");
ResultSet rs = pstmt.executeQuery();
rs.next();
double balance = rs.getDouble(1);
//如果李四餘額為負數,那麼回滾到指定儲存點
if(balance < 0) {
con.rollback(sp);
System.out.println("張三,你上當了!");
}
//提交事務
con.commit();
} catch(Exception e) {
//回滾事務
if(con != null) {
try {
con.rollback();
} catch(SQLException ex) {}
}
throw new RuntimeException(e);
} finally {
//關閉
JdbcUtils.close(con, pstmt);
}
}
4. 事務隔離級別
4.1 事務的併發讀問題
- 髒讀:讀取到另一個事務未提交資料
- 不可重複讀:兩次讀取不一致
- 幻讀(虛讀):讀到另一事務已提交資料
4.2 併發事務問題
因為併發事務導致的問題大致有5類,其中兩類是更新問題,三類是讀問題
- 髒讀(dirty read):讀到另一個事務的未提交更新資料,即讀取到了髒資料
- 不可重複讀(unrepeatable read):對同一記錄的兩次讀取不一致,因為另一事務對該記錄做了修改
- 幻讀(虛讀)(phantom read):對同一張表的兩次查詢不一致,因為另一事務插入了一條記錄
4.2.1 髒讀
事務1:張三給李四轉賬100元
事務2:李四檢視自己的賬戶
- t1:事務1:開始事務
- t2:事務1:張三給李四轉賬100元
- t3:事務2:開始事務
- t4:事務2:李四檢視自己的賬戶,看到賬戶多出100元(髒讀)
- t5:事務2:提交事務
- t6:事務1:回滾事務,回到轉賬之前的狀態
4.2.2不可重複讀
事務1:酒店檢視兩次1048號房間狀態
事務2:預訂1048號房間
- t1:事務1:開始事務
- t2:事務1:檢視1048號房間狀態為空閒
- t3:事務2:開始事務
- t4:事務2:預定1048號房間
- t5:事務2:提交事務
- t6:事務1:再次檢視1048號房間狀態為使用
- t7:事務1:提交事務
對同一記錄的兩次查詢結果不一致!
4.2.3幻讀
事務1:對酒店房間預訂記錄兩次統計
事務2:新增一條預訂房間記錄
- t1:事務1:開始事務
- t2:事務1:統計預訂記錄100條
- t3:事務2:開始事務
- t4:事務2:新增一條預訂房間記錄
- t5:事務2:提交事務
- t6:事務1:再次統計預訂記錄為101記錄
- t7:事務1:提交
對同一表的兩次查詢不一致!
不可重複讀和幻讀的區別:
- 不可重複讀是讀取到了另一事務的更新;
- 幻讀是讀取到了另一事務的插入(MySQL中無法測試到幻讀);
4.3 四大隔離級別
4個等級的事務隔離級別,在相同資料環境下,使用相同的輸入,執行相同的工作,根據不同的隔離級別,可以導致不同的結果。不同事務隔離級別能夠解決的資料併發問題的能力是不同的
4.3.1 SERIALIZABLE(序列化)
- 不會出現任何併發問題,因為它是對同一資料的訪問是序列的,非併發訪問的
- 效能最差
4.3.2 REPEATABLE READ(可重複讀)(MySQL)
- 防止髒讀和不可重複讀,不能處理幻讀問題
- 效能比SERIALIZABLE好
4.3.3 READ COMMITTED(讀已提交資料)(Oracle)
- 防止髒讀,沒有處理不可重複讀,也沒有處理幻讀;
- 效能比REPEATABLE READ好
4.3.4 READ UNCOMMITTED(讀未提交資料)
- 可能出現任何事務併發問題
- 效能最好
MySQL的預設隔離級別為REPEATABLE READ,這是一個很不錯的選擇吧!
4.3.5 MySQL隔離級別
MySQL的預設隔離級別為Repeatable read,可以通過下面語句檢視:
select @@tx_isolation
也可以通過下面語句來設定當前連線的隔離級別:
set transaction isolationlevel [4先1]
4.3.6 JDBC設定隔離級別
con. setTransactionIsolation(int level);引數可選值如下:
- Connection.TRANSACTION_READ_UNCOMMITTED
- Connection.TRANSACTION_READ_COMMITTED
- Connection.TRANSACTION_REPEATABLE_READ
- Connection.TRANSACTION_SERIALIZABLE
5. 事務總結
- 事務的特性:ACID
- 事務開始邊界與結束邊界:開始邊界(con.setAutoCommit(false)),結束邊界(con.commit()或con.rollback())
- 事務的隔離級別: READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。多個事務併發執行時才需要考慮併發事務