相關歷史文章(閱讀本文之前,您可能需要先看下之前的系列👇)
前言
對於要把事務在實際中使用好,需要了解事務的特性。
事務的四大特性主要是:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)。
一、事務的四大特性
1.1 原子性(Atomicity)
原子性是指事務是一個不可分割的工作單位,事務中的操作要麼全部成功,要麼全部失敗。比如在同一個事務中的SQL語句,要麼全部執行成功,要麼全部執行失敗。
begin transaction;
update account set money = money-100 where name = '張三';
update account set money = money+100 where name = '李四';
commit transaction;
1.2 一致性(Consistency)
官網上事務一致性的概念是:事務必須使數據庫從一個一致性狀態變換到另外一個一致性狀態。
換一種方式理解就是:事務按照預期生效,數據的狀態是預期的狀態。
舉例說明:張三向李四轉100元,轉賬前和轉賬後的數據是正確的狀態,這就叫一致性,如果出現張三轉出100元,李四賬號沒有增加100元這就出現了數據錯誤,就沒有達到一致性。
1.3 隔離性(Isolation)
事務的隔離性是多個用戶併發訪問數據庫時,數據庫爲每一個用戶開啓的事務,不能被其他事務的操作數據所幹擾,多個併發事務之間要相互隔離。
1.4 持久性(Durability)
持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即使數據庫發生故障也不應該對其有任何影響。
例如我們在使用JDBC操作數據庫時,在提交事務方法後,提示用戶事務操作完成,當我們程序執行完成直到看到提示後,就可以認定事務以及正確提交,即使這時候數據庫出現了問題,也必須要將我們的事務完全執行完成,否則就會造成我們看到提示事務處理完畢,但是數據庫因爲故障而沒有執行事務的重大錯誤。
二、數據庫ACID的體現
2.1 原子性
原子性說的是數據要麼一起成功,要麼一起失敗,那麼就有兩種情況:事務提交(commit)和事務回滾(rollback)。
我們先看下事務正常提交的情況,下面我們在數據庫模擬張三給李四轉賬成功的場景:
我們手動提交(commit)數據庫事務之後,張三給李四轉賬100元的這個業務操作算是真正成功了,張三賬戶中少了100,李四賬戶中多了100。
接下來看下事務不正常的情況下:
事務回滾之後,對於張三和李四的金額的操作都失敗了,這就確保了事務的原子性。
2.2 一致性
一致性主要說明的是事務的前後,數據庫中的數據的狀態要確保一致。
事務提交成功,那麼張三賬戶上的餘額是900元,李四賬戶上的餘額是100元。
事務提交失敗,那麼張三和李四的賬戶的金額不變。
這說明現在在數據庫的事務的控制下,確保了數據的一致性。
2.3 隔離性
隔離性的體現,多個併發事務之間是隔離的。
張三給李四轉賬,如果事務沒有提交的話,那麼在另外一個session中並不能查看另外一個session未提交的數據。
2.4 持久性
持久性的體現就是數據一旦commit之後,那麼對於數據的改變就是永久的。我們commit之後,張三的賬戶就永久減少了100元,李四的賬戶就永久增加了100元。
三、JDBC ACID的體現
我們使用JDBC連接MYSQL數據庫的代碼:
public class App {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
/*
* 1/ 註冊JDBC驅動;
* 2/ 獲取連接;
* 3/ 開啓事務
* 4/ 創建SQL語句;
* 5/ 執行SQL語句;
* 6/ 提交事務
* 7/ 關閉連接.
*
*/
// 1/ 註冊JDBC驅動;
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://127.0.0.1:3306/tx_demo";
String user = "root";
String password = "root";
// 2/ 獲取連接;
Connection conn = DriverManager.getConnection(url, user, password);
// 3/ 開啓事務
conn.setAutoCommit(false);
// 4/ 創建SQL語句; 注意:實際使用account的主鍵,這裏主要是爲了方便理解。
String sql = "update account set money = money-100 where name = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1,"張三");
// 5/ 執行SQL語句;
int rs = ps.executeUpdate();
if(rs>0) {
System.out.println("張三-扣減成功");
}
// 給李四增加金額
sql = "update account set money = money+100 where name = ?";
ps = conn.prepareStatement(sql);
ps.setString(1,"李四");
rs = ps.executeUpdate();
if(rs>0) {
System.out.println("李四-增加成功");
}
// 6/ 提交事務,更標準的寫法應該攔截異常,有異常的情況下rollback();
conn.commit();
// 7/ 關閉連接.
ps.close();
conn.close();
}
}
說明:
如果代碼正常運行的話,那麼張三會扣減金額,李四會增加金額,這就確保的原子性;
一旦數據保存到數據庫之後,數據就永久被改變了,這就是持久性;
事務前後,數據的狀態也是我們所期望的狀態,這就保證了數據的一致性;
如果在事務未commit的話,那麼在另外一個線程發起查詢請求的話,那麼並不能查看到最近的數據(這裏未進行編碼),這就是隔離性。
分佈式事務解決方案「手寫代碼」:http://t.cn/AieNUirK
點擊「閱讀原文」,快人一步,快速學習分佈式事務!