資料庫事務的四大特性以及事務的隔離級別

Java大蝸牛發表於2018-07-17

本篇講述資料庫中事務的四大特性(ACID),並且將會詳細地說明事務的隔離級別。

如果一個資料庫聲稱支援事務的操作,那麼該資料庫必須要具備以下四個特性:

⑴ 原子性(Atomicity)

原子性是指事務包含的所有操作要麼全部成功,要麼全部失敗回滾,這和前面兩篇部落格介紹事務的功能是一樣的概念,因此事務的操作如果成功就必須要完全應用到資料庫,如果操作失敗則不能對資料庫有任何影響。

⑵ 一致性(Consistency)

一致性是指事務必須使資料庫從一個一致性狀態變換到另一個一致性狀態,也就是說一個事務執行之前和執行之後都必須處於一致性狀態。

拿轉賬來說,假設使用者 A 和使用者 B 兩者的錢加起來一共是 5000,那麼不管 A 和 B 之間如何轉賬,轉幾次賬,事務結束後兩個使用者的錢相加起來應該還得是 5000,這就是事務的一致性。

⑶ 隔離性(Isolation)

隔離性是當多個使用者併發訪問資料庫時,比如操作同一張表時,資料庫為每一個使用者開啟的事務,不能被其他事務的操作所干擾,多個併發事務之間要相互隔離。

即要達到這麼一種效果:對於任意兩個併發的事務 T1 和 T2,在事務 T1 看來,T2 要麼在 T1 開始之前就已經結束,要麼在 T1 結束之後才開始,這樣每個事務都感覺不到有其他事務在併發地執行。

關於事務的隔離性資料庫提供了多種隔離級別,稍後會介紹到。

⑷ 永續性(Durability)

永續性是指一個事務一旦被提交了,那麼對資料庫中的資料的改變就是永久性的,即便是在資料庫系統遇到故障的情況下也不會丟失提交事務的操作。

例如我們在使用 JDBC 運算元據庫時,在提交事務方法後,提示使用者事務操作完成,當我們程式執行完成直到看到提示後,就可以認定事務以及正確提交,即使這時候資料庫出現了問題,也必須要將我們的事務完全執行完成,否則就會造成我們看到提示事務處理完畢,但是資料庫因為故障而沒有執行事務的重大錯誤。

以上介紹完事務的四大特性 (簡稱 ACID),現在重點來說明下事務的隔離性,當多個執行緒都開啟事務運算元據庫中的資料時,資料庫系統要能進行隔離操作,以保證各個執行緒獲取資料的準確性,在介紹資料庫提供的各種隔離級別之前,我們先看看如果不考慮事務的隔離性,會發生的幾種問題:

1  髒讀

髒讀是指在一個事務處理過程裡讀取了另一個未提交的事務中的資料。

當一個事務正在多次修改某個資料,而在這個事務中這多次的修改都還未提交,這時一個併發的事務來訪問該資料,就會造成兩個事務得到的資料不一致。例如:使用者 A 向使用者 B 轉賬 100 元,對應 SQL 命令如下

 

  1. update account set money=money+100 where name=’B’;  (此時A通知B)  

  2. update account set money=money - 100 where name=’A’;  

當只執行第一條 SQL 時,A 通知 B 檢視賬戶,B 發現確實錢已到賬(此時即發生了髒讀),而之後無論第二條 SQL 是否執行,只要該事務不提交,則所有操作都將回滾,那麼當 B 以後再次檢視賬戶時就會發現錢其實並沒有轉。

2 不可重複讀

不可重複讀是指在對於資料庫中的某個資料,一個事務範圍內多次查詢卻返回了不同的資料值,這是由於在查詢間隔,被另一個事務修改並提交了。

例如事務 T1 在讀取某一資料,而事務 T2 立馬修改了這個資料並且提交事務給資料庫,事務 T1 再次讀取該資料就得到了不同的結果,傳送了不可重複讀。

不可重複讀和髒讀的區別是,髒讀是某一事務讀取了另一個事務未提交的髒資料,而不可重複讀則是讀取了前一事務提交的資料。

在某些情況下,不可重複讀並不是問題,比如我們多次查詢某個資料當然以最後查詢得到的結果為主。但在另一些情況下就有可能發生問題,例如對於同一個資料 A 和 B 依次查詢就可能不同,A 和 B 就可能打起來了……

3 虛讀 (幻讀)

幻讀是事務非獨立執行時發生的一種現象。例如事務 T1 對一個表中所有的行的某個資料項做了從 “1” 修改為 “2” 的操作,這時事務 T2 又對這個表中插入了一行資料項,而這個資料項的數值還是為 “1” 並且提交給資料庫。而操作事務 T1 的使用者如果再檢視剛剛修改的資料,會發現還有一行沒有修改,其實這行是從事務 T2 中新增的,就好像產生幻覺一樣,這就是發生了幻讀。

幻讀和不可重複讀都是讀取了另一條已經提交的事務(這點就髒讀不同),所不同的是不可重複讀查詢的都是同一個資料項,而幻讀針對的是一批資料整體(比如資料的個數)。

現在來看看 MySQL 資料庫為我們提供的四種隔離級別:

① Serializable (序列化):可避免髒讀、不可重複讀、幻讀的發生。

② Repeatable read (可重複讀):可避免髒讀、不可重複讀的發生。

③ Read committed (讀已提交):可避免髒讀的發生。

④ Read uncommitted (讀未提交):最低階別,任何情況都無法保證。

以上四種隔離級別最高的是 Serializable 級別,最低的是 Read uncommitted 級別,當然級別越高,執行效率就越低。像 Serializable 這樣的級別,就是以鎖表的方式 (類似於 Java 多執行緒中的鎖) 使得其他的執行緒只能在鎖外等待,所以平時選用何種隔離級別應該根據實際情況。在 MySQL 資料庫中預設的隔離級別為 Repeatable read (可重複讀)。

在 MySQL 資料庫中,支援上面四種隔離級別,預設的為 Repeatable read (可重複讀);而在 Oracle 資料庫中,只支援 Serializable (序列化) 級別和 Read committed (讀已提交) 這兩種級別,其中預設的為 Read committed 級別。

在 MySQL 資料庫中檢視當前事務的隔離級別:

 

  1. select @@tx_isolation; 

在 MySQL 資料庫中設定事務的隔離 級別:

 

  1. set  [glogal | session]  transaction isolation level 隔離級別名稱;  

  2. set tx_isolation=’隔離級別名稱;’ 

例 1:檢視當前事務的隔離級別:

例 2:將事務的隔離級別設定為 Read uncommitted 級別:

或:

記住:設定資料庫的隔離級別一定要是在開啟事務之前!

如果是使用 JDBC 對資料庫的事務設定隔離級別的話,也應該是在呼叫 Connection 物件的 setAutoCommit(false) 方法之前。呼叫 Connection 物件的 setTransactionIsolation(level) 即可設定當前連結的隔離級別,至於引數 level,可以使用 Connection 物件的欄位:

在 JDBC 中設定隔離級別的部分程式碼:

後記

隔離級別的設定只對當前連結有效。對於使用 MySQL 命令視窗而言,一個視窗就相當於一個連結,當前視窗設定的隔離級別只對當前視窗中的事務有效;對於 JDBC 運算元據庫來說,一個 Connection 物件相當於一個連結,而對於 Connection 物件設定的隔離級別只對該 Connection 物件有效,與其他連結 Connection 物件無關。

覺得有收穫的話大家也可以加 Java 高階交流 群: 725633148  裡面會分享一些資深架構師錄製的影片錄影:有 Spring,MyBatis,Netty原始碼分析,高併發、高效能、分散式、微服務架構的原理,JVM效能最佳化、分散式架構等這些成為架構師必備的知識體系。還能領取免費的學習資源,目前受益良多!

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31545684/viewspace-2157983/,如需轉載,請註明出處,否則將追究法律責任。

相關文章