MSSQL-併發控制-2-Isolation

 
 


 
    若是轉載,請註明博文來源:  www.cnblogs.com/xinysu/   ,版權歸 博客園 蘇家小蘿蔔 全部。望各位支持!
 


       MySQL經過MVCC和鎖來實現併發控制,在4個隔離級別中,讀寫數據方式及加鎖方式有所不一樣,以知足不一樣的業務需求。html

    而在MSSQL中,也是經過鎖和MVCC的行版原本實現併發控制。
    每一個事務中,鎖的類型、級別、加鎖、釋放的狀況,由事務的隔離級別控制,在MSSQL中,有6個隔離級別,不一樣的隔離級別對鎖的應用不同。而這兩個隔離級別中,有2個應用 MVCC的機制,也就是 快照類的隔離級別:Read Commmitted Snapshot 跟 Snapshot。

1 併發控制理論

    在MSSQL中,常常用到的併發控制理論是 悲觀併發控制跟樂觀併發控制。

1.1 悲觀併發控制

    悲觀併發,默認在事務操做過程當中,必定會有其餘事務跟它爭奪資源,因此在事務操做過程當中,會根據不一樣的狀況對數據添加鎖,避免操做期間其餘事務對該數據的修改或讀取,保證數據的一致性。
     悲觀併發控制,因爲歸入了鎖機制,很大程度會影響到併發規模。主要應用於數據頻繁修改、而且回滾事務的成本要大於鎖數據的成本 的系統中

1.2 樂觀併發控制

    樂觀控制,默認事務在讀取數據的時候,其餘事務並無在操做這些數據,因此不會加鎖,直接修改數據,修改後查看讀取數據期間是否有其餘用戶也修改了數據,若是有,則回滾自己的修改事務。
     樂觀併發控制,應用於數據修改不頻繁、而且 回滾事務成本要小於鎖數據成本 的系統中 

2 隔離級別

    在每個事務中,都指定了一個隔離級別,該隔離級別定義了這個事務跟其餘事務之間的隔離程度。
 
    在MSSQL中,有6種隔離級別,4個常規隔離級別跟2個快照隔離級別:Read UnCommitted、Read Committed、Read Commmitted (行版本)、Read Repeattable、Snapshot跟Read Serializeble。Read Commmitted (行版本)跟Snapshot 可能接觸狀況比較少,不過仍會說明。
 
    在MySQL中,默認的隔離級別是RR,而在SQL SERVER中,默認的隔離級別是RC,讀已提交。

2.1 隔離級別說明

     如何設置整個數據庫的默認隔離級別?   
 
    數據不一致的說明詳見以前博文: http://www.cnblogs.com/xinysu/p/7260227.html 中的第四章:數據不一致狀況。
 
    下文中說S鎖,並非所有加鎖過程(MSSQL中仍是IS鎖的申請)。
  1. Read UnCommitted
    • 簡稱 RU,讀未提交記錄,始終是讀最新記錄
    • 可能存在髒讀、不可重複讀、幻讀等問題
    • 讀的過程不加S鎖,等同於 SELECT * FROM tbname with(nolock)
  2. Read Committed
    • 簡稱 RC ,讀已提交記錄
    • 可能存在不可重複讀、幻讀等問題
    • 讀的過程加 S鎖,不管事務是否結束,SELECT 語句一旦結束,立馬釋放S鎖,不會等到事務結束才釋放鎖,遵循的是 Strict 2-PL
  3. Read Commmitted (行版本)
    • 簡稱 RCSI
    • 應用MVCC原理,版本讀,讀已提交記錄,可是讀取到的不必定是最新的記錄
    • 同個事務中,讀取數據都是同一個版本
    • 不存在髒讀、不可重複讀問題,可能存在幻讀問題
    • 行版本控制隔離級別 中的版本數據,不存在與數據庫自己,而是存在 tempdb ,下文會詳細描述這一隔離級別
  4. Read Repeattable
    • 簡稱 RR ,可重複讀記錄
    • 可能存在幻讀等問題
    • 讀的過程加S鎖,直到事務結束,才釋放S鎖,遵循的是 Stong Strict 2-PL
  5. Snapshot
    • 簡稱 SI
    • 下文會詳細描述這一隔離級別
  6. Read Serializeble
    • 簡稱 RS,序列化讀記錄
    • 不存在 髒讀、不可重複讀、幻讀等問題
    • 讀的過程當中除了添加S鎖,還添加範圍鎖;修改數據的過程當中,除了添加 X 鎖,也會添加範圍鎖,避免在符合條件的數據在操做過程當中,有其餘符合條件的數據INSERT進來
    • 併發度最差,除非明確業務需求及性能影響才使用,曾經遇到過某個短信業務的框架默認使用這個隔離級別,上線後爆發死鎖上K個,立刻分析緊急修復....

2.2 Read Commmitted Snapshot Isolation 與 Snapshot Isolation

    Read Commmitted Snapshot Isolation 使用行版本控制 語句級的快照,在事務中當數據發生修改或者刪除時,調用寫入複製機制,保證寫入的行數據的舊版本知足事務操做前的一致性。 RCSI 保證的是語句級的 讀一致性。
    Snapshot Isolation 使用行版本控制 事務級的快照,當事務開始的時候,調用寫入複製機制。 SI 保證的是事務級 的讀取一致性。
         
      如何管理行版本信息呢?
     二者的行版本的信息均存儲在tempdb數據庫內,並不是存儲在自己的數據庫,這就要求tempdb要有足夠的空間存儲版本信息,若是tempdb空間不足,則行版本寫入失敗,形成該隔離級別沒法正常使用。
     存儲引擎對使用 RCSI 或者 SI 隔離級別的事務,在 SI事務開始的時候,分配一個事務序列號 XLN,每次分配遞增1,以此實現事務級的一致性,這裏注意 RCSI的 事務序列號 並非一個事務一個序列號,而是事務內每條SQL一個事務序列號,以此來實現語句級別的快照。這兩個隔離級別下,須要維護全部執行過數據修改的邏輯副本(即行版本),這些邏輯副本存儲在tempdb內,每一個邏輯副本(行版本)都有標記本次的事務的事務序列號XLN。即 最新的行值存儲在當前的數據庫中,而歷史行版本信息包括最新版本,存儲在tempdb中。這裏注意一下,事務內的修改數據寫行版本信息的時候,先寫入到緩存池中,在刷新到tempdb文件,避免性能形成太大的影響。
    
     這個時候,可能會問?那豈不是tempdb要存儲很是多的歷史版本數據,有沒有刪除機制呢?
    這個是有的,一方面,行版本信息不會即時刪除,由於要保證基於行版本控制隔離級別下運行的事務要求,保證並行的事務若是正在使用tempdb的行版本信息 不會受到影響。另外一方面,數據庫的存儲引擎 會跟蹤最先可用的事務序列號,而後按期刪除比序列號更小的 XLN的全部行版本。
      
       如何讀取行版本信息呢?
      兩個快照隔離級別下的 的事務讀數據的時候,不會獲取正在讀取數據上的共享鎖,所以不會堵塞正在修改的事務,因爲減小了鎖的申請及數量,能夠提供其DB併發能力。不過會獲取所在表格的架構鎖,若是表格正在發現架構修改(如列增長修改等),則會被堵塞。
      如何讀取合適的行版本,RCSI 跟 SI 之間是有區別的。
      RCSI: 每次啓動語句時,提交全部數據,同時讀取tempdb中的 最新事務序列,這使 RCSI 下事務內的每一個語句 均可以查看 每一個語句啓動時存在的 最新數據的快照,也就是 事務內多個SQL查詢間隙中有其餘事務修改了數據,那麼同個事務的屢次相同SQL查詢結果就會出現不一致的狀況。
      SI:每次 啓動事務時,提交全部數據,讀取 最接近但低於 自己的 快照事務序列號,也就是 事務內的多個SQL 查詢,讀到的數據都是同一個版本,即便屢次查詢間隙有其餘事務修改數據,讀到的結果也是一致的。
 
       如何修改行版本信息呢 ?
      在使用 RCSI 事務中,使用阻塞性掃描(其中讀取數據值時將在數據行上採用更新鎖(U 鎖)完成選擇要更新的行,知足條件的行記錄將升級更新鎖到排它鎖,注意,這裏掃描的不是tempdb裏邊的行版本信息,而是實際數據庫裏邊的最新行記錄,修改數據的機制跟 RC 相同。 若是數據行不符合更新條件,則在該行上將釋放更新鎖,同時鎖定下一行並對其進行掃描。持有鎖以後,則進行數據更新,事務結束後,釋放鎖。
 
      在使用 SI 事務中,對數據修改採用樂觀方法:使用行版本的數據,進行數據修改,直到數據修改完成是,才獲取實際數據上的鎖, 當數據行符合更新標準時,則提交修改的數據行。  若是數據行已在快照事務之外修改,則將出現更新衝突,同時快照事務也將終止。 更新衝突由數據庫引擎處理,沒法禁用更新衝突檢測。
 
      從簡單的SQL來分析,WHERE條件均爲主鍵(僅爲我的測試推測):
  • 同個事務,屢次 SELECT  * FROM tbname WHERE id=2
    • RCSI,在同個事務中,每一個SQL啓動的時候,提交數據到tempdb表格(我的推測,應該是會分配一個相似hash字符串之類的,若是同個事務中的屢次查詢結果一致,應該不用在每一個SQL開始的時候,重複提交行版本到tempdb),從tempdb中讀取最新版本信息,若是tempdb沒有版本信息,則從 數據庫中讀取,並把讀取到的記錄存儲在 tempdb。會存在同個事務中,屢次讀取數據結果不一致的狀況。
    • SI,在同個事務中,同個事務內的相同SQL 從tempdb中讀取距離當前事務最新的版本,整個事務內部的SQL都是用這個版本數據,若是tempdb沒有版本信息,則從 數據庫中讀取,並把讀取到的記錄存儲在 tempdb。同個事務中,不會存在 屢次讀取數據結果不一致的狀況。
  • UPDATE tbname SET colname='xinysu' WHERE id=18
    • RCSI,直接讀取數據庫中的數據,根據主鍵加上X鎖,更新數據,這個操做跟 RC 隔離級別是同樣的。
    • SI,讀取 行版本 數據,在行版本上選擇須要更新的行,修改爲功後把數據 修改到實際的數據庫中去,若是 實際數據庫中的數據在這段操做期間已被其餘事務修改了數值,則會出現更新衝突,該事務將報錯中止。即,SI 在 UPDATE 的時候,有更新衝突檢測。
      • 爲啥要先在行版本上更新,最後在更新到實際數據上?
      • 假設一個UPDATE運行須要3s,可是隻更新了1條行記錄,若是直接在實際數據上更新,則須要鎖定掃描記錄3s,最後更新,中間會堵塞到其餘事務對該數據的查詢,可是若是在行版本上更新,則不須要鎖住 實際數據,最後更新1行記錄的時候,很是快,避免長時間的堵塞,提升併發能力
屬性
使用行版本控制的已提交讀隔離級別
快照隔離級別
數據庫級選項啓動 
READ_COMMITTED_SNAPSHOT
ALLOW_SNAPSHOT_ISOLATION
事務設置
使用默認的已提交讀隔離級別,或運行 SET TRANSACTION ISOLATION LEVEL 語句來指定 READ COMMITTED 隔離級別
 SET TRANSACTION ISOLATION LEVEL 來在事務啓動前指定 SNAPSHOT 隔離級別
行版本處理
在每條語句啓動前提交的全部數據。
在每一個事務啓動前提交的全部數據。
更新處理
從行版本恢復到實際的數據,以選擇要更新的行並使用選擇的數據行上的更新鎖。 獲取要修改的實際數據行上的排他鎖。 沒有更新衝突檢測。
使用行版本選擇要更新的行。 嘗試獲取要修改的實際數據行上的排他鎖,若是數據已被其餘事務修改,則出現更新衝突,同時快照事務也將終止。
更新衝突檢測
集成支持。 沒法禁用。

3 隔離級別測試

    查看當前會話的數據庫隔離級別:DBCC USEROPTIONS ,查看[set options] = 'isolation level',便可查看當前事務的隔離級別。
    數據不一致的說明詳見以前博文: http://www.cnblogs.com/xinysu/p/7260227.html 中的第四章:數據不一致狀況。
    2-PL鎖申請釋放的說明詳見以前博文: http://www.cnblogs.com/xinysu/p/7260227.html 中的第3章:數據不一致狀況。
 
    設置數據庫隔離級別:
  • RU,事務開始的時候,設置 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
  • RC,事務開始的時候,設置 SET TRANSACTION ISOLATION LEVEL READ COMMITTED
  • RCSI,整個數據庫級設置 READ_COMMITTED_SNAPSHOT 爲ON,注意,設置的這個的時候須要獲取數據庫的獨佔權,也就是當前不容許有用戶線程鏈接數據庫,否者這個設置SQL會一直處於堵塞狀況。若是當前數據庫的默認隔離級別是 RC,則設置後,默認爲RCSI,否者,須要在事務開始的時候,設置 SET TRANSACTION ISOLATION LEVEL READ COMMITTED
    • 數據庫設置:當前數據庫下,執行 ALTER DATABASE dbname SET READ_COMMITTED_SNAPSHOT ON
    • 事務設置:SET TRANSACTION ISOLATION LEVEL READ COMMITTED
  • RR,事務開始的時候,設置 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
  • RS,事務開始的時候,設置 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
  • SI,整個數據庫級設置 ALLOW_SNAPSHOT_ISOLATION 爲ON,同時設置事務的隔離級別爲 SNAPSHOT。注意,這裏的 ALLOW_SNAPSHOT_ISOLATION 設置也是須要獲取數據的獨佔鎖。
    • 數據庫設置:當前數據庫下,執行 ALTER DATABASE dbname SET ALLOW_SNAPSHOT_ISOLATION ON
    • 事務設置:SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
 
    測試過程當中,分爲3個表格:無索引、有索引、有惟一索引。
 
CREATE TABLE tb_no_index ( id int primary key not null identity(1,1), age int not null, name varchar(100) );
CREATE TABLE tb_index ( id int primary key not null identity(1,1), age int not null, name varchar(100) );
CREATE TABLE tb_unique_index ( id int primary key not null identity(1,1), age int not null,name varchar(100) );
 
CREATE INDEX IX_age ON tb_index(age)
CREATE INDEX IX_unique_age ON tb_index(age)
 
INSERT INTO tb_no_index(age) values(2),(9),(21),(4),(7),(25);
INSERT INTO tb_index(age) values(2),(9),(21),(4),(7),(25);
INSERT INTO tb_unique_index(age) values(2),(9),(21),(4),(7),(25);

3.1 Read Uncommitted

  • 數據不一致狀況測試截圖 
  • RU測試結論
    • 在RU隔離級別下
      • 不會出現更新丟失狀況(鎖機制),可是會出現 髒讀、不可重複讀及幻讀的狀況。
      • 讀不加行鎖,能夠讀未提交數據

3.2 Read Committed

  • 數據不一致狀況測試截圖
  • 讀狀況測試
  • RC測試結論
    • 在RC隔離級別下
      • 不會出現更新丟失狀況(鎖機制)、髒讀現象,可是會出現 不可重複讀及幻讀的狀況
      • 讀須要申請鎖,故不會出現髒讀狀況
      • 遵循 強2-PL模式,事務內的讀鎖讀完即刻釋放,寫鎖等到事務提交的時候才釋放。

3.3 Read Commit Snapshot Isolation

  • 測試環境設置
    • 實現設置數據庫隔離級別爲:
    • 檢查當前會話的默認隔離級別:
  • 數據不一致狀況測試截圖
  • 更新衝突測試
  • RCSI 測試結論
    • 讀不加鎖,但申請表格的架構鎖,讀行版本數據
    • 不存在丟失更新、髒讀狀況,可是存在不可重複讀及幻讀狀況
    • 沒有更新衝突檢測,RCSI跟RC的更新處理方式同樣

3.4 Read Reaptable

  • 數據不一致狀況測試截圖
  • RR測試結論
    • 讀加S鎖,事務結束後才釋放S鎖
    • 不存在丟失更新、髒讀及不可重複讀狀況,可是存在幻讀狀況

3.5 Read Serializable

  • 數據不一致狀況測試截圖
  • RS 測試結論
    • 讀加S鎖,事務結束後才釋放S鎖
    • 增長了範圍鎖
    • 不存在丟失更新、髒讀、不可重複讀、幻讀狀況
    • 併發能力最差

3.6 Snapshot Isolation

  • 數據不一致狀況測試截圖
  • 更新衝突測試
  • SI 測試結論
    • 不存在 丟失更新、髒讀、幻讀等數據不一致狀況
    • 讀不加鎖,爲讀行版本數據
    • 具備衝突監測,沒法禁用,若是使用這個隔離級別,程序要作更新衝突的回滾處理

4 總結

隔離級別
說明
髒讀
不可重複讀
幻影
併發控制模型
Read UnCommitted
未提交讀
YES
YES
YES
悲觀
Read Committed
已提交讀
NO
YES
YES
悲觀
Read Commmitted (行版本)
已提交讀(快照)
NO
YES
YES
樂觀
Read Repeattable
可重複讀
NO
NO
YES
悲觀
Snapshot
快照
NO
NO
NO
樂觀
Read Serializeble
可串行化
NO
NO
NO
悲觀
相關文章
相關標籤/搜索