sqlserver 隔離級別 - 轉

SQL-92標準中定義了四個隔離級別,這四個隔離級別在之前版本的SQL Server中即受到支持:數據庫

READ UNCOMMITTED

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

READ COMMITTED

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

實驗1

query1:事務1spa

複製代碼
--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鎖在語句執行完之後就被釋放code

query2:事務2blog

複製代碼
--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稱爲不可重複讀,由於同一個事物內的兩次相同的查詢操做的結果可能不一樣。ip

REPEATABLE READ

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

實驗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隔離級別保證了在相同的查詢條件下,同一個事務中的兩個查詢,第二次讀取的內容確定包換第一次讀到的內容。

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級別的基礎上,添加一個範圍鎖。保證一個事務內的兩次查詢結果徹底同樣,而不會出現第一次查詢結果是第二次查詢結果的子集。
相關文章
相關標籤/搜索