一、什麼是事務?
在人員管理系統中,你刪除一個人員,你即需要刪除人員的基本資料,也要刪除和該人員相關的信息,如信箱,文章等等,這樣,這些數據庫操作語句就構成一個事務!
一個事務是由一條或多條對數據庫操作的SQL語句所組成的一個不可分割的工作單元,只有當事務中的所有操作都正常執行完了,整個事務纔可以被提交給數據庫。
二、事務是必須滿足4個條件(ACID)
- 事務的原子性( Atomicity):一組事務,要麼成功;要麼撤回。
- 一致性 (Consistency):事務執行後,數據庫狀態與其他業務規則保持一致。比如A向B轉賬,不可能A扣了錢,B卻沒收到。
- 隔離性(Isolation):事務獨立運行。一個事務處理後的結果,影響了其他事務,那麼其他事務會撤回。事務的100%隔離,需要犧牲速度。比如A正在從一張銀行卡中取錢,在A取錢結束前,B不能向這張卡轉賬。
- 持久性(Durability):事務完成後,事務對數據庫的所有更新將被保存到數據庫,不能回滾。
三、MySQL中的事務
在默認情況下,MySQL每執行一條SQL語句,都是一個單獨的事務。如果需要在一個事務中包含多條SQL語句,那麼需要開啓事務和結束事務。
- 開啓事務:start transaction
- 結束事務:commit或rollback
在執行SQL語句之前,先執行start transaction,這就開啓了一個事務(事務的起點),然後可以去執行多條SQL語句,最後要結束事務,commit表示提交,即事務中的多條SQL語句所作出的影響會持久到數據庫中,或者rollback,表示回滾到事務的起點,之前做的所有操作都被撤銷了。
mysql> SELECT * FROM account;
+----+------+---------+
| id | NAME | balance |
+----+------+---------+
| 1 | zs | 1000.00 |
| 2 | ls | 1000.00 |
| 3 | ww | 1000.00 |
+----+------+---------+
3 rows in set (0.00 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE account SET balance=900 WHERE name = 'zs';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT * FROM account;
+----+------+---------+
| id | NAME | balance |
+----+------+---------+
| 1 | zs | 900.00 |
| 2 | ls | 1000.00 |
| 3 | ww | 1000.00 |
+----+------+---------+
3 rows in set (0.00 sec)
mysql> UPDATE account SET balance=1100 WHERE name = 'ls';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT * FROM account;
+----+------+---------+
| id | NAME | balance |
+----+------+---------+
| 1 | zs | 900.00 |
| 2 | ls | 1100.00 |
| 3 | ww | 1000.00 |
+----+------+---------+
3 rows in set (0.00 sec)
mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM account;
+----+------+---------+
| id | NAME | balance |
+----+------+---------+
| 1 | zs | 1000.00 |
| 2 | ls | 1000.00 |
| 3 | ww | 1000.00 |
+----+------+---------+
3 rows in set (0.00 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE account SET balance=balance-100 WHERE name = 'zs';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT * FROM account;
+----+------+---------+
| id | NAME | balance |
+----+------+---------+
| 1 | zs | 900.00 |
| 2 | ls | 1000.00 |
| 3 | ww | 1000.00 |
+----+------+---------+
3 rows in set (0.00 sec)
mysql> UPDATE account SET balance=balance+100 WHERE name = 'ls';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT * FROM account;
+----+------+---------+
| id | NAME | balance |
+----+------+---------+
| 1 | zs | 900.00 |
| 2 | ls | 1100.00 |
| 3 | ww | 1000.00 |
+----+------+---------+
3 rows in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.02 sec)
mysql> SELECT * FROM account;
+----+------+---------+
| id | NAME | balance |
+----+------+---------+
| 1 | zs | 900.00 |
| 2 | ls | 1100.00 |
| 3 | ww | 1000.00 |
+----+------+---------+
3 rows in set (0.00 sec)
四、JDBC事務
在JDBC中處理事務,都是通過Connection完成的。
同一事務中所有的操作,都在使用同一個Connection對象。
①JDBC中的事務
Connection的三個方法與事務有關:
- setAutoCommit(boolean):設置是否爲自動提交事務,如果true(默認值爲true)表示自動提交,也就是每條執行的SQL語句都是一個單獨的事務,如果設置爲false,表示開啓事務,禁止自動提交;con.setAutoCommit(false) 表示開啓事務。
- commit():提交結束事務。
- rollback():回滾結束事務。
JDBC處理事務的代碼格式:
try{
con.setAutoCommit(false);//開啓事務
......
con.commit();//try的最後提交事務
} catch() {
con.rollback();//回滾事務
}
示例:
public class AccountDao {
/*
* 修改指定用戶的餘額
* */
public void updateBalance(Connection con, String name,double balance) {
try {
String sql = "UPDATE account SET balance=balance+? WHERE name=?";
PreparedStatement pstmt = con.prepareStatement(sql);
pstmt.setDouble(1,balance);
pstmt.setString(2,name);
pstmt.executeUpdate();
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
import cn.itcast.jdbc.JdbcUtils;
import org.junit.Test;
import java.sql.Connection;
import java.sql.SQLException;
public class Demo1 {
/*
* 演示轉賬方法
* 所有對Connect的操作都在Service層進行的處理
* 把所有connection的操作隱藏起來,這需要使用自定義的小工具(day19_1)
* */
public void transferAccounts(String from,String to,double money) {
//對事務的操作
Connection con = null;
try{
con = JdbcUtils.getConnection();
con.setAutoCommit(false); //開啓事務,禁止自動提交
AccountDao dao = new AccountDao();
dao.updateBalance(con,from,-money);//給from減去相應金額
if (true){
throw new RuntimeException("不好意思,轉賬失敗");
}
dao.updateBalance(con,to,+money);//給to加上相應金額
//提交事務
con.commit();
} catch (Exception e) {
try {
con.rollback();
} catch (SQLException e1) {
e.printStackTrace();
}
throw new RuntimeException(e);
}
}
@Test
public void fun1() {
transferAccounts("zs","ls",100);
}
}
五、事務隔離級別
1、事務的併發讀問題
- 髒讀:讀取到另外一個事務未提交數據(不允許出來的事);
- 不可重複讀:兩次讀取不一致;
- 幻讀(虛讀):讀到另一事務已提交數據。
2、併發事務問題
因爲併發事務導致的問題大致有5類,其中兩類是更新問題三類是讀問題。
- 髒讀(dirty read):讀到另一個事務的未提交新數據,即讀取到了髒數據;
例如當事務A與事務B併發執行時,當事務A更新後,事務B查詢讀到A尚未提交的數據,此時事務A回滾,則事務B讀到的數據是無效的“髒”數據。(事務A讀取了事務B更新的數據,然後B回滾操作,那麼A讀取到的數據是髒數據)
- 不可重複讀(unrepeatable):對同一記錄的兩次讀取不一致,因爲另一事務對該記錄做了修改;
例如事務A與事務B併發執行時,當事務B查詢讀取數據後,事務A更新操作更改事務B查詢到的數據,此時事務B再次讀該數據,發現前後兩次數據不一樣。
- 幻讀(虛讀)(phantom read):對同一張表的兩次查詢不一致,因爲另一事務插入了一條記錄。
例如當事務A與事務B併發執行時,當事務B查詢讀取數據後,事務A新增或刪除了一條滿足事務A的查詢條件的記錄,此時事務B再次查詢,發現查詢到前次不存在的記錄,或者前次的某個記錄不見了。
3、四大隔離級別
4個等級的事務隔離級別,在相同的數據環境下,使用相同的輸入,執行相同的工作,根據不同的隔離級別,可以導致不同的結果。不同事務隔離級別能夠解決的數據併發問題的能力是不同的。
1、SERIALIZABLE(串行化)
- 不會出現任何併發問題,因爲它是對同一數據的訪問是串行的,非併發訪問的;
- 性能最差
2、REPEATABLE READ(可重複讀)(MySQL)
- 防止髒讀和不可重複讀,不能處理幻讀
- 性能比SERIALIZABLE好
3、READ COMMITTED(讀已提交數據)(Oracle)
- 防止髒讀,不能處理不可重複讀和幻讀;
- 性能比REPEATABLE READ好
4、READ UNCOMMITTED(讀未提交數據)
- 可能出現任何事物併發問題,什麼都不處理。
- 性能最好
六、MySQL隔離級別
MySQL的默認隔離級別爲Repeatable read,可以通過下面語句查看:
SELECT @@`TX_ISOLATION`;
也可以通過下面語句來設置當前連接的隔離級別:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ ;//[4選1]
七、JDBC設置隔離級別
con.setTransactionIsolation(int level) :參數可選值如下:
- Connection.TRANSACTION_NONE JDB;不支持事務
- Connection.TRANSACTION_READ_UNCOMMITTED;未提交讀。說明在提交前一個事務可以看到另一個事務的變化,這樣讀“髒”數據、不可重複讀或者虛讀都是允許的。
- Connection.TRANSACTION_READ_COMMITTED;已提交讀。說明讀取未提交的數據是不允許的。這個級別仍然允許不可重複讀和虛讀產生。
- Connection.TRANSACTION_REPEATABLE_READ;可重複讀。說明事務保證能夠再次讀取相同的數據而不會失敗,但虛讀仍然會出現。
- Connection.TRANSACTION_READ_SERIALIZABLE。可序列化。是最高的事務級別,它防止讀“髒”數據、不可重複讀和虛讀。