1. 程式人生 > >資料庫事務的四大特性與隔離級別及測試

資料庫事務的四大特性與隔離級別及測試

四大特性

⑴ 原子性(Atomicity)

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

⑵ 一致性(Consistency)

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

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

⑶ 隔離性(Isolation)

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

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

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

⑷ 永續性(Durability)

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

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

隔離級別

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

1,髒讀

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

例:
建立一個表

INSERT INTO `kedb`.`test_table`
(`id`,
`age`)
VALUES
(<{id: }>,
<{age: }>);

插入一個數據:

INSERT
INTO `kedb`.`test_table` ( `age`) VALUES ( 5);

這裡寫圖片描述

資料庫隔離級別操作:

-- 獲取全域性隔離級別  
SELECT @@global.tx_isolation;  
--獲取會話隔離級別  
SELECT @@tx_isolation; 

--全域性隔離級別設定  
SET GLOBAL TRANSACTION ISOLATION LEVEL [  
           READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE];  
--會話隔離級別設定  
SET SESSION TRANSACTION ISOLATION LEVEL [  
            READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE];  

檢視資料庫的隔離級別

select @@global.tx_isolation;

這裡寫圖片描述

修改級別為read uncommitted;

set gloabal transaction isolation level read uncommitted;

再檢視:

select @@global.tx_isolation;

這裡寫圖片描述

這裡寫圖片描述

在終端A開啟一個修改事務:
這裡寫圖片描述
但是還未提交

這時開啟另一個終端B,查詢:
這裡寫圖片描述

終端A進行回滾事務:
這裡寫圖片描述

現在查詢:
這裡寫圖片描述

(B可以認為是客戶端,A可認為是服務端)
可以發現A的事務還未提交時,B就去獲取了,B獲取的是個當前狀態的,但B並不知道A的事務是否已經提交。這樣如果A事務進行回滾資料就不會發生改變,而B卻得到了一個改變後的資料,這就是髒讀。

那麼怎麼解決這一問題?
只要將隔離級別設為read uncommitted更高的級別就可以了
這裡寫圖片描述
現在再次模擬:
這裡寫圖片描述
可以發現B這時就不會再因為A事務沒提交而出現髒讀了。

2,不可重複讀

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

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

再舉個例子:
事務1:查詢有雙人床房間。99號房間,有雙人床。
事務2:將99號房間,改成單人床房間。
事務1:再次執行查詢,請求所有雙人床房間列表,99號房間不再列表中了。也就是說,事務1,可以看到其他事務所做的修改。
在不可重複讀,裡面,可以看到其他事務所做的修改,而導致2次的查詢結果不再一樣了。這裡的修改,是提交過的。也可以是沒有提交的,這種情況同時也是髒讀。
如果,資料庫系統的隔離級別。允許,不可重複讀。那麼你啟動一個事務,並做一個select查詢操作。查詢到的資料,就有可能,和你第2次,3次…n次,查詢到的資料不一樣。一般情況下,你只會做一次,select查詢,並以這一次的查詢資料,作為後續計算的基礎。因為允許出現,不可重複讀。那麼任何時候,查詢到的資料,都有可能被其他事務更新,查詢的結果將是不確定的。

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

模擬:
再次之前已經將級別設為read committed;
這裡寫圖片描述
從以上可以發現事務A在修改時,這時B開始讀取,讀取的是A修改之前的資料。沒有發生髒讀。

現在A提交事務,B再次查詢:
這裡寫圖片描述
這時B查詢到的結果就不是第一次查詢的結果了,所以出現了不可重複讀。

可能你會覺得這樣也很符合邏輯呀,注意這是非同步執行的,而且A,B同時執行,可B卻讀取了不同的資料,主要原因就是非同步。如果改成同步,A事務再修改的時候,B只能進行等待,當A事務結束後B再執行,這樣B就能得到正確的資料了。

那麼怎麼解決這問題?
可以想象就是同步了,在A執行這個事務的時候,B事務就必須進入等待,在A事務執行完後B再進行執行。
以上是分析,在InnoDB中可以設定隔離級別為repeatable read解決不可重複讀的問題。

repeatable read
讀取資料的事務將會禁止寫事務(但允許讀事務),寫事務則禁止任何其他事務。
避免了不可重複讀取和髒讀,但是有時可能出現幻讀。這可以通過“共享讀鎖”和“排他寫鎖”實現。

3,虛讀(幻讀)

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

這是最高的隔離級別,它通過強制事務排序使之不可能相互衝突,從而解決幻讀問題。簡言之,它是在每個讀的資料行上加上共享鎖。在這個級別,可能導致大量的超時現象和鎖競爭。只要操作產生了鎖,就不允許其他事務讀取和修改!

在MySQL中,實現了這四種隔離級別,分別有可能產生問題如下所示:
這裡寫圖片描述

REPEATABLE READ 是MySQL的預設事務隔離級別

REPEATABLE READ 這是MySQL的預設事務隔離級別,能確保事務在併發讀取資料時會看到同樣的資料行,解決了READ-COMMITTED隔離級別下的不可重複讀問題。mysql的InnoDB儲存引擎通過多版本併發控制(Multi_Version Concurrency Control, MVCC)機制來解決該問題。在該機制下,事務每開啟一個例項,都會分配一個版本號給它,如果讀取的資料行正在被其它事務執行DELETE或UPDATE操作(即該行上有排他鎖),這時該事物的讀取操作不會等待行上的鎖釋放,而是根據版本號去讀取行的快照資料(記錄在undo log中),這樣,事務中的查詢操作返回的都是同一版本下的資料,解決了不可重複讀問題。其原理如下圖所示:

這裡寫圖片描述

 缺陷:雖然該隔離級別下解決了不可重複讀問題,但理論上會導致另一個問題:幻讀(Phantom Read)。正如上面所講,一個事務在執行過程中,另一個事物對已有資料行的更改,MVCC機制可保障該事物讀取到的原有資料行的內容相同,但並不能阻止另一個事務插入新的資料行,這就會導致該事物中憑空多出資料行,像出現了幻讀一樣,這便是幻讀問題。

而解決幻讀問題,就得使用serializable級別了。
SERIALIZABLE 這是事務的最高隔離級別,通過強制事務排序,使之不可能相互衝突,就是在每個讀的資料行加上共享鎖來實現。在該隔離級別下,可以解決前面出現的髒讀、不可重複讀和幻讀問題,但也會導致大量的超時和鎖競爭現象,一般不推薦使用。