事務
什么是事務?
事務就是一組原子性的SQL查詢,或者說是一個獨立的工作單元。
事務的作用
事務在我們平常的CRUD(增刪改查)操作當中也許不太常用, 但是如果我們有一種需求,一組操作中必須全部成功執行,才算完成任務,只要有一個出錯了,那么所有的任務都將回到最初的狀況,恢復原樣。那么這就需要使用事務了。如: 銀行轉賬,購買飛機票......
事務的特性
事務具有4個特性(ACID):原子性(Atomicity)、一致性(Consistency)、隔離性(lsolation)、持久性(Durability)。
- 原子性(Atomicity): 事務中的邏輯要全部執行,不可分割。(原子是物理中最小單位)
- 一致性(Consistency): 指事務執行前和執行后, 數據的完整性保持一致
- 隔離性(Isolation): 指一個事務在執行的過程中不應該受其他事務的影響
- 持久性(Durability): 事務執行結束(提交或回滾), 數據都應持久化到數據中
演示事務
在MySQL命令行的默認設置下,事務都是自動提交的,即執行SQL語句后就會馬上執行commit操作。因此要顯式地開啟一個事務務須使用命令begin或start transaction,或者執行命令set autocommit = 0,用來禁止使用當前會話的自動提交。
-
命令行中演示事務
開啟事務:start transaction;
提交事務:commit; 數據將會寫到磁盤上的數據庫
回滾事務:rollback; 數據回滾,回到最初的狀態。
開啟一個事物:

我們先看看stuifo表里最初的數據:

再更新stuifo表里的派大星的性別為女並查看修改后的數據:

這里顯示性別已經修改了,但其實數據沒有真正的寫入到硬盤中,我們去MySQL管理軟件看看數據有沒有被更新:

可以發現數據沒有被更新,因為事務沒有被提交,現在我們把事務提交。

提交后,數據才會被更新,真正的寫入到硬盤里。

如果不提交,使用回滾rollback,數據就會恢復到初始狀態:

-
使用代碼演示事務
package test; import Utils.JDBCUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class demo { public static void main(String[] args) { Connection conn = null; PreparedStatement ps = null; try { conn = JDBCUtils.getConnection(); conn.setAutoCommit(false);//關閉事務默認自動提交 String sql = "update stuifo set sex = '女' where id = 2"; ps = conn.prepareStatement(sql); ps.executeUpdate(); conn.commit();//成功,提交事務 } catch (SQLException e) { try { conn.rollback();//失敗,回滾事務 } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); } finally { JDBCUtils.closeStatement(ps); JDBCUtils.closeConn(conn); } } }
安全問題
讀問題
在操作數據庫時,可能會出現讀問題,主要表現為臟讀,不可重復讀,幻讀。
-
臟讀
臟讀: 指 一個事務讀到了另一個事務還未提交的數據。
演示:
最初stuifo表中的數據為:

在兩個窗口分別開啟一個事務,首先在A窗口修改派大星性別為女,但不提交事務,然后在B窗口中查詢表stuifo。
A窗口:

B窗口:

這樣B窗口中的事務就讀取到了A窗口中的事務未提交的數據,稱之為臟讀。
-
不可重復讀
不可重復讀: 一個事務讀到了另一個事務提交的數據, 導致多次查詢結果不一致。
最初stuifo表中的數據為:

mysql默認是可重復讀的,所以我們先把A窗口的隔離級別設置為讀未提交,在兩個窗口分別開啟一個事物,首先在A窗口查詢表stuifo,然后在B窗口中修改派大星性別為女,並提交事務,再在A窗口查詢表stuifo。
A窗口:

B窗口:

A窗口:

這樣A窗口中在一個事務下前后讀取的數據不一致,稱為不可重復讀。
-
幻讀
幻讀: 一個事務讀到了另一個事務已提交的插入的數據,導致多次查詢結果不一致。
幻讀與不可重復讀類似,都是前后查詢結果不一致,但是不可重復讀針對的是update,后者針對的是insert。
寫問題
在操作數據庫時,可能會出現寫問題,主要表現為丟失更新。
丟失更新:指一個事務去修改數據庫,另一個事務也修改數據庫,最后的那個事務,不管是提交還是回滾都會造成前面一個事務的數據更新丟失。

解決丟失更新,通常有兩種方法:悲觀鎖和樂觀鎖。
-
悲觀鎖
指事務在一開始就認為丟失更新一定會發生,這是一件很悲觀的事情。具體操作步驟如下:
1.所有事務在執行操作前,先查詢一次數據,查詢語句如下:
select * from student for update ; 后面的for update其實是數據庫鎖機制 、一種排他鎖。
2.哪個事務先執行這個語句,哪個事務就持有了這把鎖,可以查詢出來數據,后面的事務再執行這條語句,不會有任何數據顯示,就只能等着。
3.一直等到前面的那個事務提交數據后,后面的事務數據才會出來,那么才可以往下接着操作。
這相當於上衛生間似的,誰先來誰就進去蹲着,后面來的人都只能等着。只有里面的人出來了,外面的人才能進去。
-
樂觀鎖
樂觀鎖是指,從來不會覺得丟失更新會發生。那么它的具體做法是什么呢?
要求程序員在數據庫中添加字段,然后在后續更新的時候,對該字段進行判定比對,如果一致才允許更新。例子如下:
1.數據庫表中,額外添加了一個version字段,用於記錄版本,默認從0開始,只要有針對表中數據進行修改的,那么version就+1。
2.開啟A事務,然后開啟B事務。
3.先執行數據庫表操作。因為以前都沒有人修改過。所以是允許A事務修改數據庫的,但是修改完畢,就把version的值變成1了。
4.B事務,這時候如果想執行修改,那么是不允許修改的。因為B事務以前是沒有查詢過數據庫內容的,所以它認為數據庫版本還是0。但是數據庫的版本經過A修改,已經是1了。所以這時候不允許修改,要求其重新查詢 。
5.B重新查詢后,將會得到version為1的數據,這份數據就是之前A事務修改的數據了,B在進行修改,也是在A的基礎上修改的。所以就不會有丟失更新的情況出現了。
隔離級別
設置事務的隔離級別可以解決上面的臟讀,不可重復讀,幻讀問題,但是隔離級別越高,執行效率越低,mysql的隔離級別默認為可重復讀。
事務隔離分為四個級別,由低到高為讀未提交(Read uncommitted)、讀提交(read committed)、可重復讀(repeatable read)和串行化(Serializable)。
✔代表可能發生,✖代表不會發生
| 隔離級別 | 臟讀 | 不可重復讀 | 幻讀 |
|---|---|---|---|
| 讀未提交(Read uncommitted) | ✔ | ✔ | ✔ |
| 讀提交(read committed) | ✖ | ✔ | ✔ |
| 可重復讀(repeatable read) | ✖ | ✖ | ✔ |
| 串行化(Serializable) | ✖ | ✖ | ✖ |
查看隔離級別:

設置隔離級別:

