前言
對於要把事務在實際中使用好,需要了解事務的特性。
事務的四大特性主要是:原子性(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的話,那么在另外一個線程發起查詢請求的話,那么並不能查看到最近的數據(這里未進行編碼),這就是隔離性。