今天晚上我們就來談談事務,事務是保證數據庫四大特性的前提,有事務纔能有效防止髒讀、幻讀、不可重複讀
爲了方便演示事務,我們需要創建一個account 賬戶表:
<span style="font-family:SimSun;">CREATE TABLE account(
id NUMBER PRIMARY KEY,
NAME VARCHAR(30),
balance NUMBER (10,2)
);
INSERT INTO account VALUES(1,'zs', 100000);
INSERT INTO account VALUES(2,'ls', 100000);
INSERT INTO account VALUES(3,'ww', 100000);
SELECT * FROM account;</span>
1、什麼是事務?
我們這裏舉一個簡單通俗的例子吧。
銀行轉賬!張三轉10000塊到李四的賬戶,這其實需要兩條SQL語句:
第一條SQL語句:給張三的賬戶減去10000元;
第二條SQL語句: 給李四的賬戶加上10000元。
如果在第一條SQL語句執行成功後,在執行第二條SQL語句之前,程序被中斷了(可能是拋出了某個異常,也可能是其他什麼原因),那麼李四的賬戶沒有加上10000元,而張三卻減去了10000元。這肯定是不行的!
你現在可能已經知道什麼是事務了吧!事務中的多個操作,要麼完全成功,要麼完全失敗!不可能存在成功一半的情況!也就是說給張三的賬戶減去10000元如果成功了,那麼給李四的賬戶加上10000元的操作也必須是成功的;否則給張三減去10000元,以及給李四加上10000元都是失敗的!
2、事務的四大特性(ACID)
事務的四大特性是:
原子性(Atomicity):事務中所有操作是不可再分割的原子單位。事務中所有操作要麼全部執行成功,要麼全部執行失敗。
一致性(Consistency):事務執行後,數據庫狀態與其它業務規則保持一致。如轉賬業務,無論事務執行成功與否,參與轉賬的兩個賬號餘額之和應該是不變的。
隔離性(Isolation):隔離性是指在併發操作中,不同事務之間應該隔離開來,使每個併發中的事務不會相互干擾。
持久性(Durability):一旦事務提交成功,事務中所有的數據操作都必須被持久化到數據庫中,即使提交事務後,數據庫馬上崩潰,在數據庫重啓時,也必須能保證通過某種機制恢復數據。
這個在面試的時候經常會被問到,所以想應付面試問得事務就得了解這四大特性。3、JDBC事務
在jdbc中處理事務,都是通過Connection完成的!
同一事務中所有的操作,都必須使用同一個Connection對象!
1、JDBC中的事務
Connection的三個方法與事務相關:
1、setAutoCommit(boolean):設置是否爲自動提交事務,如果true(默認值就是true)表示自動提交,也就是每條執行的SQL語句都是一個單獨的事務,如果設置false,那麼就相當於開啓了事務了;con.setAutoCommit(false)表示開啓事務!!!
2、commit():提交結束事務;con.commit();表示提交事務
3、rollback():回滾結束事務。con.rollback();表示回滾事務
jdbc處理事務的代碼格式:
try {
con.setAutoCommit(false);//開啓事務…
….
…
con.commit();//try的最後提交事務
} catch() {
con.rollback();//回滾事務
}
下面我們寫一個java的例子<span style="font-family:SimSun;">/**
* 轉賬方法
* @param from 從某賬戶
* @param to 轉到某賬戶
* @param money 轉賬金額
*/
public void transferAcc(String from, String to, double money) {
// 對事務的操作必須使用Connection對象!
Connection con = null;
try {
con = JdbcUtils.getConnection();
//開啓事務
con.setAutoCommit(false);
AccountDao dao = new AccountDao();
int i = dao.updateBalance(con, from, -money);//給from減去相應金額
if(i>0){
System.out.println("說明轉出成功.但此時還沒開始轉入!");
throw new RuntimeException("不好意思,停電!檢查數據是否已經回滾");
}
int ii = dao.updateBalance(con, to, money);//給to加上相應金額
if(ii>0){
System.out.println("說明轉入成功");
}
//提交事務
con.commit();
con.close();
} catch(Exception e) {
//回滾事務
try {
con.rollback();
con.close();
} catch (SQLException e1) {
}
throw new RuntimeException(e);
}
} </span>
其中JdbcUtils是我的dao工具類,爲了避免本文的臃腫,稍後我會在另一篇博文寫這個類。
我們可以自己寫一個類然後調用這個例子測試一下事務。簡單來說如果只要有一條sql沒成功事務就會回滾。
4、事務隔離級別
1、 併發事務問題的發生
要了解事物的隔離級別,就要先了解事務併發會引起的問題,爲什麼需要隔離,因爲併發事務導致的問題大致有5類,其中兩類是更新問題,三類是讀問題。
1、 髒讀(dirty read):讀到另一個事務的未提交更新數據,即讀取到了髒數據;
2、不可重複讀(unrepeatable read):對同一記錄的兩次讀取不一致,因爲另一事務對該記錄做了修改;
3、 幻讀(虛讀)(phantom read):對同一張表的兩次查詢不一致,因爲另一事務插入了一條記錄;
下面簡單列舉一下這些三類問題發生的場景:
髒讀
事務1:張三給李四轉賬100元
事務2:李四查看自己的賬戶
t1:事務1:開始事務
t2:事務1:張三給李四轉賬100元
t3:事務2:開始事務
t4:事務2:李四查看自己的賬戶,看到賬戶多出100元(髒讀)
t5:事務2:提交事務
t6:事務1:回滾事務,回到轉賬之前的狀態
不可重複讀
事務1:酒店查看兩次1048號房間狀態
事務2:預訂1048號房間
t1:事務1:開始事務
t2:事務1:查看1048號房間狀態爲空閒
t3:事務2:開始事務
t4:事務2:預定1048號房間
t5:事務2:提交事務
t6:事務1:再次查看1048號房間狀態爲使用
t7:事務1:提交事務
對同一記錄的兩次查詢結果不一致!
幻讀
事務1:對酒店房間預訂記錄兩次統計
事務2:添加一條預訂房間記錄
t1:事務1:開始事務
t2:事務1:統計預訂記錄100條
t3:事務2:開始事務
t4:事務2:添加一條預訂房間記錄
t5:事務2:提交事務
t6:事務1:再次統計預訂記錄爲101記錄
t7:事務1:提交
對同一表的兩次查詢不一致!
不可重複讀和幻讀的區別:
1、不可重複讀是讀取到了另一事務的更新;
2、幻讀是讀取到了另一事務的插入(MySQL中無法測試到幻讀);
爲了防止出現上述這些情況就有了隔離級別。
2、四大隔離級別
4個等級的事務隔離級別,在相同數據環境下,使用相同的輸入,執行相同的工作,根據不同的隔離級別,可以導致不同的結果。不同事務隔離級別能夠解決的數據併發問題的能力是不同的。
SERIALIZABLE
不會出現任何併發問題,因爲它是對同一數據的訪問是串行的,非併發訪問的,性能最差;
REPEATABLE READ(可重複讀)(MySQL)
防止髒讀和不可重複讀,不能處理幻讀問題,性能比SERIALIZABLE好。
READ COMMITTED(讀已提交數據)(Oracle)
防止髒讀,沒有處理不可重複讀,也沒有處理幻讀,性能比REPEATABLE READ好。
READ UNCOMMITTED(讀未提交數據)
啥也不處理!可能出現任何事務併發問題性能最好
MySQL的默認隔離級別爲REPEATABLE READ
Oracle 默認隔離級別爲READ COMMITTED
3、如何在JDBC設置隔離級別?
con.setTransactionIsolation(int level)
參數可選值如下:
Connection.TRANSACTION_READ_UNCOMMITTED;
Connection.TRANSACTION_READ_COMMITTED;
Connection.TRANSACTION_REPEATABLE_READ;
Connection.TRANSACTION_SERIALIZABLE。
事務總結:
1、事務的特性:ACID;
2、事務開始邊界與結束邊界:開始邊界(con.setAutoCommit(false)),結束邊界(con.commit()或con.rollback());
3、事務的隔離級別:READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。多個事務併發執行時才需要考慮併發事務。