事務
- 事務簡介
-
- 資料庫開啟事務的指令
- 結束事務
- 書寫結構
- 執行個體
- *事務四個基本特征或 ACID 特性:
- *Spring的事務傳播特性(屬性、機制)
- *資料庫事務并發Concurrent問題
- *事物隔離級别
事務簡介
事物是指邏輯上的一組操作,組成這組操作的各個單元,要麼全部執行成功,要麼全部實行失敗。
例如:張三–李四轉賬
-- 事物
CREATE TABLE account(
id INT PRIMARY KEY auto_increment,
`name` VARCHAR(10),
money DOUBLE
);
INSERT INTO account(name,money) VALUES('張三',1000),('李四',1000);
-- 張三給李四轉賬1000塊錢
UPDATE account SET money=money-1000 WHERE name='張三';
UPDATE account SET money=money+1000 WHERE name='李四';
資料庫開啟事務的指令
Connection接口下面的方法:
void setAutoCommit(boolean autoCommit);
autoCommit - 為 true 表示啟用自動送出模式;為 false 表示禁用自動送出模式(預設情況下就是true)
手動送出事物。
復原(出現異常的時候,所有已經執行成功的代碼需要回退到事務開始前的狀态)
預設情況下,Connetion對象處于自動送出模式下,這意味着他在執行每個語句後都會自動送出更改。
如果禁用自動送出模式,那麼送出更改就必須顯示調用commit方法,否則就無法儲存資料庫的更改。
結束事務
COMMIT 或 ROLLBACK
COMMIT 表示送出,即事務中的多條 SQL 語句所更改的資料會持久化到資料庫中。或者 ROLLBACK 表示復原,即復原到事務的起點,将之前所做的所有操作撤銷。
ROLLBACK 可以結束事務,但不代表會将資料持久化到資料庫中,而隻有 COMMIT送出才可以将資料持久化到資料庫中。
書寫結構
private Connection conn = null;
private PreparedStatement ps = null;
try {
conn.setAutoCommit(false); //将自動送出設定為false
ps.executeUpdate("修改SQL"); //執行修改操作
ps.executeQuery("查詢SQL"); //執行查詢操作
conn.commit(); //當兩個操作成功後手動送出
} catch (Exception e) {
conn.rollback(); //一旦其中一個操作出錯都将復原,使兩個操作都不成功
e.printStackTrace();
}
執行個體
public void test1() {
Connection connection = null;
PreparedStatement preparedStatement = null;
String sql1 = "UPDATE account SET money=money-1000 WHERE name='張三'";
String sql2 = "UPDATE account SET money=money+1000 WHERE name='李四'";
try {
connection = JDBCUtil.getConnection();
// 為false,表示禁用自動送出(預設情況是true)
connection.setAutoCommit(false);
preparedStatement = connection.prepareStatement(sql1);
System.out.println(preparedStatement);
preparedStatement.executeUpdate();
// ArithmeticException: / by zero
int i = 3 / 0;
preparedStatement = connection.prepareStatement(sql2);
System.out.println(preparedStatement);
preparedStatement.executeUpdate();
// setAutoCommit(false)改成false之後,不會送出資料庫,隻有調用connection.commit()才送出
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
try {
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
JDBCUtil.close(connection, preparedStatement, null);
}
}
*事務四個基本特征或 ACID 特性:
ACID,是指資料庫管理系統(DBMS)在寫入或更新資料的過程中,為保證事務(transaction tx)是正确可靠的,所必須具備的四個特性:
原子性(atomicity,或稱不可分割性)、一緻性(consistency)、隔離性(isolation,又稱獨立性)、持久性(durability)。
在資料庫系統中,一個事務是指:由一系列資料庫操作組成的一個完整的邏輯過程。例如銀行轉帳,從原賬戶扣除金額,以及向目标賬戶添加金額,這兩個資料庫操作的總和,構成一個完整的邏輯過程,不可拆分。這個過程被稱為一個事務,具有ACID特性。
1、Atomicity(原子性):一個事務(transaction)中的所有操作,或者全部完成,或者全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被復原(Rollback)到事務開始前的狀态,就像這個事務從來沒有執行過一樣。即,事務不可分割、不可約簡。
2、Consistency(一緻性):在事務開始之前和事務結束以後,資料庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設限制、觸發器、級聯復原等。
3、Isolation(隔離性):資料庫允許多個并發事務同時對其資料進行讀寫和修改的能力,隔離性可以防止多個事務并發執行時由于交叉執行而導緻資料的不一緻。事務隔離分為不同級别,包括未送出讀(Read uncommitted)、送出讀(read committed)、可重複讀(repeatable read)和串行化(Serializable)。
4、Durability(持久性):事務處理結束後,對資料的修改就是永久的,即便系統故障也不會丢失。
*Spring的事務傳播特性(屬性、機制)
當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如:一個方法運作在了一個開啟了事務的方法中時,目前方法是使用原來的事務還是開啟一個新的事務。
事務的傳播行為可以由傳播屬性指定。Spring定義了7種類傳播行為(加粗的三個最重要):
PROPAGATION_REQUIRED:需要, 如果存在一個事務,則支援目前事務, 如果沒有事務則開啟(原來有就使用原來事物,沒有就建立事物,一般Service的中的方法裡面有更新的操作操作)
PROPAGATION_SUPPORTS:支援, 如果存在一個事務,支援目前事務。如果沒有事務,則非事務的執行(原來有就使用原來事物,沒有就算了,一般Service的中的方法裡面都是查詢操作)。
PROPAGATION_MANDATORY: 必要的 如果已經存在一個事務,支援目前事務。如果沒有一個活動的事務,則抛出異常。
PROPAGATION_REQUIRES_NEW:總是開啟一個新的事務。如果一個事務已經存在,則将這個存在的事務挂起。
PROPAGATION_NOT_SUPPORTED:總是非事務地執行,并挂起任何存在的事務。
PROPAGATION_NEVER:絕不 總是非事務地執行,如果存在一個活動事務,則抛出異常。
PROPAGATION_NESTED:嵌套的 如果有就嵌套、沒有就開啟事務
事務傳播屬性可以在@Transactional注解的propagation屬性中定義。
*資料庫事務并發Concurrent問題
假設現在有兩個事務Transaction01和Transaction02并發執行。
1、髒讀(目前事物讀到了其他事物更新但是還沒有送出的值就是髒讀)
①Transaction01将某條記錄的AGE值從20修改為30。
②Transaction02讀取了Transaction01更新後的值:30。Read Uncommited
③Transaction01復原,AGE值恢複到了20。④Transaction02讀取到的30就是一個無效的值。
隻有髒讀是讀取了别人更新但是還沒有送出到資料庫的資料,不可重複讀和幻讀都是讀取已經送出資料庫的資料。
2、不可重複讀
①Transaction01讀取了AGE值為20。
②Transaction02将AGE值修改為30。
③Transaction01再次讀取AGE值為30,和第一次讀取的不一樣
這樣就發生了在一個事務内兩次讀到的資料是不一樣的,是以稱為是不可重複讀。
3、幻讀
①Transaction01讀取了STUDENT表中的一部分資料
②Transaction02向STUDENT表中插入了新的行
③Transaction01讀取了STUDENT表時,多出了一些行。
不可重複讀和幻讀讀到的都是已經送出到資料庫的真實資料,和髒讀比起來并不是什麼大問題。
*事物隔離級别
資料庫系統必須具有隔離并發運作各個事務的能力,使它們不會互相影響,避免各種并發問題。一個事務與其他事務隔離的程度稱為隔離級别。SQL标準中規定了多種事務隔離級别,不同隔離級别對應不同的幹擾程度,隔離級别越高,資料一緻性就越好,但并發性越弱。
1、讀未送出:READ UNCOMMITTED
允許Transaction01讀取Transaction02未送出的修改。
2、讀已送出:READ COMMITTED
要求Transaction01隻能讀取Transaction02已送出的修改。
3、可重複讀:REPEATABLE READ
確定Transaction01可以多次從一個字段中讀取到相同的值,即Transaction01執行期間禁止其它事務對這個字段進行更新。
4、串行化:SERIALIZABLE
確定Transaction01可以多次從一個表中讀取到相同的行,在Transaction01執行期間,禁止其它事務對這個表進行添加、更新、删除操作。可以避免任何并發問題,但性能十分低下。(單線程,在一個請求中,其他線程讀都不可以)
可以在@Transactional的isolation屬性中設定隔離級别,并不是隔離級别越高越好,一般是READ COMMITTED讀已送出。
Isolation.REPEATABLE_READ:可重複讀,MySQL預設的隔離級别
Isolation.READ_COMMITTED:讀已送出,Oracle預設的隔離級别,開發時通常使用的隔離級别
在項目中一般用讀已送出(Read Commited)這個隔離級别
MyBatis有緩存,讀取後再次讀取時讀到的其實是緩存裡的内容,是以不會有變化。
測試時候現将MyBatis的緩存關閉掉:
#禁用二級緩存
mybatis.configuration.cache-enabled=false
#一級緩存指定為statement級别
mybatis.configuration.local-cache-scope=statement