一、事務(Transaction)概述
- 其實指的一組操作,裏面包含許多個單一的邏輯。只要有一個邏輯沒有執行成功,那麼都算失敗。 所有的數據都回歸到最初的狀態(回滾)。
- 爲什麼要有事務?
爲了確保邏輯的成功。 如: 銀行轉賬。
二、演示事務
開啓事務:
start transaction;
提交:
commit; 提交事務, 數據將會寫到磁盤上的數據庫
回滾:
rollback ; 數據回滾,回到最初的狀態。
- 通過conn.setAutoCommit(false )來關閉自動提交的設置。
- 提交事務 conn.commit();
- 回滾事務 conn.rollback();
@Test
public void testTransaction(){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtil.getConn();
//連接,事務默認就是自動提交的。 關閉自動提交。
conn.setAutoCommit(false);
String sql = "update account set money = money - ? where id = ?";
ps = conn.prepareStatement(sql);
//扣錢, 扣ID爲1 的100塊錢
ps.setInt(1, 100);
ps.setInt(2, 1);
ps.executeUpdate();
int a = 10 /0 ;//有了異常,下面代碼就不會執行了
//加錢, 給ID爲2 加100塊錢
ps.setInt(1, -100);
ps.setInt(2, 2);
ps.executeUpdate();
//成功: 提交事務。
conn.commit();
} catch (SQLException e) {
try {
//失敗: 回滾事務
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally {
JDBCUtil.release(conn, ps, rs);
}
}
三、事務特性ACID(面試)
- 原子性(Atom)
指的是 事務中包含的邏輯,不可分割。
- 一致性(Consistent)
指的是 事務執行前後,數據完整性。
- 隔離性(Isolate)
指的是 事務在執行期間不應該受到其他事務的影響。
- 持久性(Durable)
指的是 事務執行成功,那麼數據應該持久保存到磁盤上。
四、安全問題&隔離級別(面試)
讀 問題
- 髒讀
一個事務讀到另外一個事務還未提交的數據。
- 不可重複讀
一個事務讀到了另外一個事務提交的數據 ,造成了前後兩次查詢結果不一致。
- 幻讀
一個事務讀到了另一個事務已提交的插入的數據,導致多次查詢結果不一致。
寫 問題
- 丟失更新:
B事務如果提交,會造成A事務的操作無效。
B事務回滾,也會造成A事務更新失效。
- 解決方法:悲觀鎖,樂觀鎖
- 悲觀鎖
指事務在一開始就認爲丟失更新一定會發生, 這是一件很悲觀的事情。 具體操作步驟如下:
3. 所以事務在執行操作前,先查詢一次數據, 查詢語句如下:
select * from student for update ;
後面的for update 其實是數據庫鎖機制 、 一種排他鎖。
4. 哪個事務先執行這個語句, 哪個事務就持有了這把鎖, 可以查詢出來數據, 後面的事務想再執行這條語句,不會有任何數據顯示,就只能等着。
5. 一直等到前面的那個事務提交數據後, 後面的事務數據才會出來,那麼纔可以往下接着操作。
補充:就像排隊上廁所一樣,只有裏面的人出來了,才能進去。 這其實就是 java 中的同步的概念。
- 樂觀鎖
樂觀鎖是指,從來不會覺得丟失更新會發生。要求程序員在數據庫中添加字段,然後在後續更新的時候,對該字段進行判定比對, 如果一致才允許更新。
例:
1. 數據庫表中,額外添加了一個version字段, 用於記錄版本, 默認從0 開始, 只要有針對表中數據進行修改的,那麼version就+1.
2. 開啓A事務, 然後開啓B事務 。
3. A 先執行數據庫表操作。 因爲以前都沒有人修改過。 所以是允許A事務修改數據庫的,但是修改完畢,就把version的值變成 1 了 。
4. B事務, 這時候如果想執行修改,那麼是不允許修改的。 因爲B事務以前是沒有查詢過數據庫內容的,所以它認爲數據庫版本還是0 。 但是數據庫的版本經過A修改,已經是1了。
所以這時候不允許修改, 要求其重新查詢 。
5. B重新查詢後, 將會得到version 爲 1的數據,這份數據就是之前A 事務修改的數據了, B 在進行修改,也是在A的基礎上修改的。 所以就不會有丟失更新的情況出現了。
補充:樂觀鎖的機制 ,其實是通過比對版本或者比對字段的方式來實現的,與版本控制軟件【SVN , GIT】機制是一樣的。
隔離級別
- 按效率劃分,從高到低
讀未提交 > 讀已提交 > 可重複讀 > 可串行化
- 按攔截程度 ,從高到底
可串行化 > 可重複讀 > 讀已提交 > 讀未提交
- Read Uncommited【讀未提交】
指的是 : 一個事務可以讀取到另一個事務還未提交的數據。 這就會引發 “髒讀” 讀取到的是數據庫內存中的數據,而並非真正磁盤上的數據。
例子:
1. 開啓一個命令行窗口A, 開始事務,然後查詢表中記錄。
設置當前窗口的事務隔離級別爲:讀未提交 命令如下:
set session transaction isolation level read uncommitted;
2. 另外在打開一個窗口B, 也開啓事務, 然後執行 sql 語句, 但是不提交
3. 在A窗口重新執行查詢, 會看到B窗口沒有提交的數據。
- Read Commited 【讀已提交】
與前面的讀未提交剛好相反,這個隔離級別是 ,只能讀取到其他事務已經提交的數據,那些沒有提交的數據是讀不出來的。屏蔽了髒讀的情況,但是這會造成一個問題是: 前後讀取到的結果不一樣。 發生了不可重複!!!, 所謂的不可重複讀,就是不能執行多次讀取,否則出現結果不一樣。
例子:
1. 開啓一個命令行窗口A, 開始事務,然後查詢表中記錄。
設置當前窗口的事務隔離級別爲:讀已提交 命令如下:
set session transaction isolation level read committed;
2. 另外在打開一個窗口B, 也開啓事務, 然後執行 sql 語句, 但是不提交
3. 在A窗口重新執行查詢, 是不會看到B窗口剛纔執行sql 語句的結果,因爲它還沒有提交。
4. 在B窗口執行提交。
5. 在A窗口中執行查看, 這時候纔會看到B窗口已經修改的結果。
6. 但是這會造成一個問題是: 在A窗口中, 第一次查看數據和第二次查看數據,結果不一樣。
- Repeatable Read 【重複讀】
MySql 默認的隔離級別就是這個。該隔離級別, 可以讓事務在自己的會話中重複讀取數據,並且不會出現結果不一樣的狀況,即使其他事務已經提交了,也依然還是顯示以前的數據。(讀到的不是最新更新的數據,確保本事務不受其他事務影響)
例子:
1. 開啓一個命令行窗口A, 開始事務,然後查詢表中記錄。
2. 設置當前窗口的事務隔離級別爲:重複讀 命令如下:
set session transaction isolation level repeatable read;
3. 另外在打開一個窗口B, 也開啓事務, 然後執行 sql 語句, 但是不提交
4. 在A窗口重新執行查詢, 是不會看到B窗口剛纔執行sql 語句的結果,因爲它還沒有提交。
5. 在B窗口執行提交。
6. 在A窗口中執行查看, 這時候查詢結果,和以前的查詢結果一致。不會發生改變。
- Serializable 【可串行化(序列化)】
該事務級別是最高級的事務級別了,如果有一個連接設置隔離級別爲可串行化,那麼誰先打開事務,誰就有了先執行的權利,誰後打開事務,就只能等着,等前面的那個事務,提交或者回滾後纔會執行。這種隔離級別比前面幾種都要強大一點,也就是前面幾種的問題【髒讀、不可重複讀、幻讀】都能夠解決。但是都使用該隔離級別也會有些問題。 比如造成併發的性能問題。 其他的事務必須得等當前正在操作表的事務先提交,才能接着往下,否則只能一直在等着。所以比較少用,容易造成性能上的問題,效率比較低。
例子:
1. 開啓一個命令行窗口A, 開始事務,然後查詢表中記錄。
2. 設置當前窗口的事務隔離級別爲:serializable 命令如下:
set session transaction isolation level read serializable;
3. 另外在打開一個窗口B, 也開啓事務, 然後執行 sql 語句, 但是不提交
4. 在A窗口重新執行查詢, 會卡主,沒有任何信息顯示。
5. 在B窗口執行提交。
6. 在A窗口中執行查看, 這時候纔會顯示結果。
五、總結
- 在代碼裏面使用事務
conn.setAutoCommit(false);
conn.commit();
conn.rollback();
- 事務只是針對連接連接對象,如果再開一個連接對象,那麼那是默認的提交。
- 事務是會自動提交的。
- 安全隱患:
讀
髒讀:一個事務讀到了另一個事務未提交的數據。
不可重複讀:一個事務讀到了另一個事務已提交的數據,造成前後兩次查詢結果不一致。
幻讀:一個事務讀到了另一個事務insert的數據 ,造成前後查詢結果不一致 。
寫
丟失更新。
- 隔離級別
讀未提交
> 引發問題: 髒讀
讀已提交
> 解決: 髒讀 , 引發: 不可重複讀
可重複讀
> 解決: 髒讀 、 不可重複讀 , 未解決: 幻讀
可串行化
> 解決: 髒讀、 不可重複讀 、 幻讀。 導致:性能下降
- 補充:
mySql 默認的隔離級別是 可重複讀
Oracle 默認的隔離級別是 讀已提交