數據庫事務隔離級別--讀未提交,讀已提交,重複讀,序列化

參考原文:https://my.oschina.net/bigdataer/blog/1976010html

上一篇文章講述了:數據庫主從複製,那麼新的問題數據庫讀寫分離對事物是否有影響?數據庫

1. 名詞session

  • 讀未提交read-uncommited併發

  • 讀已提交read-commitedoracle

  • 重複讀repeatable--》可能產生主從數據不一致問題性能

  • 串行化serializable--》特殊場景-秒殺使用,通常不用spa

數據庫事務的隔離級別有4個,由低到高依次爲Read uncommitted、Read committed、Repeatable read、Serializable,這四個級別可能產生和解決以下問題:髒讀、不可重複讀、幻讀。.net

事務的隔離級別 髒讀 不可重複讀 幻讀
Read uncommitted
Read committed--Sql Server , Oracle ×
Repeatable read--MySQL × ×
Serializable × × ×

2.注意:咱們討論隔離級別的場景,主要是在多個事務併發的狀況下,所以,接下來的講解都圍繞事務併發。 code

3.Read uncommitted 讀未提交htm

  1. 公司發工資了,領導把5000元打到singo的帳號上,可是該事務並未提交,而singo正好去查看帳戶,發現工資已經到帳,是5000元整,很是高興。但是不幸的是,領導發現發給singo的工資金額不對,是2000元,因而迅速回滾了事務,修改金額後,將事務提交,最後singo實際的工資只有2000元,singo空歡喜一場。
  2. 出現上述狀況,即咱們所說的髒讀,兩個併發的事務,「事務A:領導給singo發工資」、「事務B:singo查詢工資帳戶」,事務B讀取了事務A還沒有提交的數據。
  3. 當隔離級別設置爲Read uncommitted時,就可能出現髒讀,如何避免髒讀,請看下一個隔離級別。

4.Read committed 讀提交

  1. singo拿着工資卡去消費,系統讀取到卡里確實有2000元,而此時她的老婆也正好在網上轉帳,把singo工資卡的2000元轉到另外一帳戶,並在singo以前提交了事務,當singo扣款時,系統檢查到singo的工資卡已經沒有錢,扣款失敗,singo十分納悶,明明卡里有錢,爲什麼......
  2. 出現上述狀況,即咱們所說的不可重複讀,兩個併發的事務,「事務A:singo消費」、「事務B:singo的老婆網上轉帳」,事務A事先讀取了數據,事務B緊接了更新了數據,並提交了事務,而事務A再次讀取該數據時,數據已經發生了改變。
  3. 當隔離級別設置爲Read committed時,避免了髒讀,可是可能會形成不可重複讀。
  4. 大多數數據庫的默認級別就是Read committed,好比Sql Server , Oracle。如何解決不可重複讀這一問題,請看下一個隔離級別。

5.Repeatable read 重複讀

  1. 當隔離級別設置爲Repeatable read時,能夠避免不可重複讀。當singo拿着工資卡去消費時,一旦系統開始讀取工資卡信息(即事務開始),singo的老婆就不可能對該記錄進行修改,也就是singo的老婆不能在此時轉帳。
  2. 雖然Repeatable read避免了不可重複讀,但還有可能出現幻讀。
  3. singo的老婆工做在銀行部門,她時常經過銀行內部系統查看singo的信用卡消費記錄。有一天,她正在查詢到singo當月信用卡的總消費金額(select sum(amount) from transaction where month = 本月)爲80元,而singo此時正好在外面胡吃海塞後在收銀臺買單,消費1000元,即新增了一條1000元的消費記錄(insert transaction ... ),並提交了事務,隨後singo的老婆將singo當月信用卡消費的明細打印到A4紙上,卻發現消費總額爲1080元,singo的老婆很詫異,覺得出現了幻覺,幻讀就這樣產生了。
  4. 注:MySQL的默認隔離級別就是Repeatable read。

6.Serializable 序列化

  Serializable是最高的事務隔離級別,同時代價也花費最高,性能很低,通常不多使用,在該級別下,事務順序執行,不只能夠避免髒讀、不可重複讀,還避免了幻像讀。

.........

7.名詞解釋

7.1 READ UNCOMMITTED

READ UNCOMMITTED是限制性最弱的隔離級別,由於該級別忽略其餘事務放置的鎖。使用READ UNCOMMITTED級別執行的事務,能夠讀取還沒有由其餘事務提交的修改後的數據值,這些行爲稱爲「髒」讀。這是由於在Read Uncommitted級別下,讀取數據不須要加S鎖,這樣就不會跟被修改的數據上的X鎖衝突。好比,事務1修改一行,事務2在事務1提交以前讀取了這一行。若是事務1回滾,事務2就讀取了一行沒有提交的數據,這樣的數據咱們認爲是不存在的。

實驗

(1)打開一個客戶端A,並設置當前事務模式爲read uncommitted(未提交讀),查詢表account的初始值:

 

(2)在客戶端A的事務提交以前,打開另外一個客戶端B,更新表account:

 

(3)這時,雖然客戶端B的事務還沒提交,可是客戶端A就能夠查詢到B已經更新的數據:

 

(4)一旦客戶端B的事務由於某種緣由回滾,全部的操做都將會被撤銷,那客戶端A查詢到的數據其實就是髒數據:

 

(5)在客戶端A執行更新語句update account set balance = balance - 50 where id =1,lilei的balance沒有變成350,竟然是400,是否是很奇怪,數據不一致啊,若是你這麼想就太天真 了,在應用程序中,咱們會用400-50=350,並不知道其餘會話回滾了,要想解決這個問題能夠採用讀已提交的隔離級別(不可重複讀(read-committed))

7.2 READ COMMITTED

READ COMMITTED(Nonrepeatable reads)是SQL Server默認的隔離級別。該級別經過指定語句不能讀取其餘事務已修改可是還沒有提交的數據值,禁止執行髒讀。在當前事務中的各個語句執行之間,其餘事務仍能夠修改、插入或刪除數據,從而產生沒法重複的讀操做,或「影子」數據。好比,事務1讀取了一行,事務2修改或者刪除這一行而且提交。若是事務1想再一次讀取這一行,它將得到修改後的數據或者發現這同樣已經被刪除,所以事務的第二次讀取結果與第一次讀取結果不一樣,所以也叫不可重複讀。

實驗1

query1:事務1

--step1:建立實驗數據
select * into Employee from AdventureWorks.HumanResources.Employee
alter table Employee add constraint pk_Employee_EmployeeID primary key(EmployeeID)
 
--step2:設置隔離級別,這是數據庫的默認隔離界別
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
 
--step3:開啓第一個事務
BEGIN TRAN tran1
   --step4:執行select操做,查看VacationHours,對查找的記錄加S鎖,在語句執行完之後自動釋放S鎖
   SELECT EmployeeID, VacationHours
       FROM Employee
       WHERE EmployeeID = 4;
 
   --step5:查看當前加鎖狀況,沒有發如今Employee表上面有鎖,這是由於當前的隔離界別是READ COMMITTED
   --在執行完step2之後立刻釋放了S鎖.
   SELECT request_session_id, resource_type, resource_associated_entity_id,
       request_status, request_mode, resource_description
       FROM sys.dm_tran_locks

查看鎖的狀況以下圖所示,咱們發如今只有在數據庫級別的S鎖,而沒有在表級別或者更低級別的鎖,這是由於在Read Committed級別下,S鎖在語句執行完之後就被釋放。

 

query2:事務2

--step6:開啓第二個事務
BEGIN TRAN tran2;
   --step7:修改VacationHours,須要得到排它鎖X,在VacationHours上沒有有S鎖
   UPDATE Employee
       SET VacationHours = VacationHours - 8  
       WHERE EmployeeID = 4;
 
   --step8:查看當前加鎖狀況
   SELECT request_session_id, resource_type, resource_associated_entity_id,
       request_status, request_mode, resource_description
       FROM sys.dm_tran_locks

在開啓另一個update事務之後,咱們再去查看當前的鎖情況,以下圖所示,咱們發如今表(Object)級別上加了IX鎖,在這張表所在的Page上也加了IX鎖,由於表加了彙集索引,因此在葉子結點上加了X鎖,這個鎖的類型是KEY。

 

而後咱們回到事務1當中再次執行查詢語句,咱們會發現查詢被阻塞,咱們新建一個查詢query3來查看這個時候的鎖情況,其查詢結果以下,咱們能夠發現查詢操做須要在KEY級別上申請S鎖,在Page和表(Object)上面申請IS鎖,可是由於Key上面原先有了X鎖,與當前讀操做申請的S鎖衝突,因此這一步處於WAIT狀態。

 

若是此時提交事務2的update操做,那麼事務1的select操做再也不被阻塞,獲得查詢結果,可是咱們發現此時獲得的查詢結果與第一次獲得的查詢結果不一樣,這也是爲何將read committed稱爲不可重複讀,由於同一個事物內的兩次相同的查詢操做的結果可能不一樣。

7.3 REPEATABLE READ

REPEATABLE READ是比READ COMMITTED限制性更強的隔離級別。該級別包括READ COMMITTED,而且另外指定了在當前事務提交以前,其餘任何事務均不能夠修改或刪除當前事務已讀取的數據。併發性低於 READ COMMITTED,由於已讀數據的共享鎖在整個事務期間持有,而不是在每一個語句結束時釋放。好比,事務1讀取了一行,事務2想修改或者刪除這一行而且提交,可是由於事務1還沒有提交,數據行中有事務1的鎖,事務2沒法進行更新操做,所以事務2阻塞。若是這時候事務1想再一次讀取這一行,它讀取結果與第一次讀取結果相同,所以叫可重複讀。

實驗2

query1:事務1

--step1:建立實驗數據
select * into Employee from AdventureWorks.HumanResources.Employee
alter table Employee add constraint pk_Employee_EmployeeID primary key(EmployeeID)
 
--step2:設置隔離級別
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
 
--step3:開啓第一個事務
BEGIN TRAN tran1
   --step4:執行select操做,查看VacationHours
   SELECT EmployeeID, VacationHours
       FROM Employee
       WHERE EmployeeID = 4;
 
   --step5:查看當前加鎖狀況,發如今Employee表上面有S鎖,這是由於當前的隔離界別是REPEATABLE READ
   --S鎖只有在事務執行完之後纔會被釋放.
   SELECT request_session_id, resource_type, resource_associated_entity_id,
       request_status, request_mode, resource_description
       FROM sys.dm_tran_locks

查詢鎖狀態的結果以下圖所示,咱們發如今KEY上面加了S鎖,在Page和Object上面加了IS鎖,這是由於在Repeatable Read級別下S鎖要在事務執行完之後纔會被釋放。

 

 query2:事務2

--step6:開啓第二個事務
BEGIN TRAN tran2;
   --step7:修改VacationHours,須要得到排他鎖X,在VacationHours上有S鎖,出現衝突,因此update操做被阻塞
   UPDATE Employee
       SET VacationHours = VacationHours - 8  
       WHERE EmployeeID = 4;

執行上述update操做的時候發現該操做被阻塞,這是由於update操做要加排它鎖X,而由於原先的查詢操做的S鎖沒有釋放,因此二者衝突。咱們新建一個查詢3執行查詢鎖狀態操做,發現結果以下圖所示,咱們能夠發現是WAIT發生在對KEY加X鎖的操做上面。

 

此時再次執行查詢1中的select操做,咱們發現查詢結果跟第一次相同,因此這個叫作可重複讀操做。可是可重複讀操做並非特定指兩次讀取的數據如出一轍,Repeatable Read存在的一個問題是幻讀,就是第二次讀取的數據返回的條目數比第一次返回的條目數更多。

好比在Repeatable Read隔離級別下,事務1第一次執行查詢select id from users where id>1 and id <10,返回的結果是2,4,6,8。這個時候事務1沒有提交,那麼對2,4,6,8上面依然保持有S鎖。此時事務2執行一次插入操做insert into user(id) valuse(3),插入成功。此時再次執行事務1中的查詢,那麼返回結果就是2,3,4,6,8。這裏的3就是由於幻讀而出現的。所以能夠得出結論:REPEATABLE READ隔離級別保證了在相同的查詢條件下,同一個事務中的兩個查詢,第二次讀取的內容確定包換第一次讀到的內容。

7.4 SERIALIZABLE 

SERIALIZABLE 是限制性最強的隔離級別,由於該級別鎖定整個範圍的鍵,並一直持有鎖,直到事務完成。該級別包括REPEATABLE READ,並增長了在事務完成以前,其餘事務不能向事務已讀取的範圍插入新行的限制。好比,事務1讀取了一系列知足搜索條件的行。事務2在執行SQL statement產生一行或者多行知足事務1搜索條件的行時會衝突,則事務2回滾。這時事務1再次讀取了一系列知足相同搜索條件的行,第二次讀取的結果和第一次讀取的結果相同。

重複讀與幻讀

重複讀是爲了保證在一個事務中,相同查詢條件下讀取的數據值不發生改變,可是不能保證下次一樣條件查詢,結果記錄數不會增長。

幻讀就是爲了解決這個問題而存在的,他將這個查詢範圍都加鎖了,因此就不能再往這個範圍內插入數據,這就是SERIALIZABLE 隔離級別作的事情。

隔離級別與鎖的關係

  1. 在Read Uncommitted級別下,讀操做不加S鎖;
  2. 在Read Committed級別下,讀操做須要加S鎖,可是在語句執行完之後釋放S鎖;
  3. 在Repeatable Read級別下,讀操做須要加S鎖,可是在事務提交以前並不釋放S鎖,也就是必須等待事務執行完畢之後才釋放S鎖。
  4. 在Serialize級別下,會在Repeatable Read級別的基礎上,添加一個範圍鎖。保證一個事務內的兩次查詢結果徹底同樣,而不會出現第一次查詢結果是第二次查詢結果的子集。
相關文章
相關標籤/搜索