1. 程式人生 > >MSSQL-並發控制-2-Isolation

MSSQL-並發控制-2-Isolation

ora eat 在操作 -c ransac class 線程 .html 跟蹤



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

MySQL通過MVCC和鎖來實現並發控制,在4個隔離級別中,讀寫數據方式及加鎖方式有所不同,以滿足不同的業務需求。

而在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 悲觀

MSSQL-並發控制-2-Isolation