SQL Server 阻塞分析

1、加鎖(locking)、阻塞(blocking)、死鎖(deadlock)定義
 
     加鎖:用於管理多個鏈接的進程。當鏈接須要訪問一塊數據時,在這些數據上放置某種類型的鎖。
     阻塞:指一個鏈接須要訪問一塊數據時,必須等待另外一個鏈接的鎖解除。
     死鎖:指兩個鏈接造成被稱爲"僵局"的形式,它們互相等待對方的鎖解除。
 
     在 SQL Server 中,每一個鏈接均可以看做是一個單獨的會話。
 
 
2、數據庫鎖
 
一、鎖粒度
 
     爲了改善併發性,SQL Server 實現以下資源級別上的鎖粒度:
  • 行(RID)
  • 關鍵字(KEY)
  • 頁面(PAG)
  • 區(EXT)
  • 堆或 B 樹(HoBT)
  • 表(TAB)
  • 數據庫(DB)
 
(1)行級鎖
     該鎖是在一個表的單獨行上維護,是數據庫表上最低級別的鎖。當查詢表中的一行數據時,查詢被授予該行的 RID 鎖。
     由於阻塞被限制在所影響的行,全部行級鎖提供了很高的並行性。
 
(2)鍵級鎖
     這是索引中的行級鎖,被標識爲一個關鍵字(KEY)鎖。
     對於具備彙集索引的表,表的數據頁面和彙集索引的葉子頁面相同。由於表和彙集索引的行相同,從表(或彙集索引)中訪問行時,在該彙集索引行或有限範圍的行中只能得到一個關鍵字鎖。
     該鎖是針對有彙集索引的表,與行級鎖類型,也有很高的併發性。
 
(3)頁級鎖
     這種鎖在表或索引的單一頁面中維護,被標識爲 PAG 鎖。當查詢請求一個頁面中的多行時,請求的全部行的一致性能夠經過得到單獨行上的 RID/KEY 鎖或整個頁面上的一個 PAG 鎖來維護。在查詢計劃中,鎖管理器肯定得到多個 RID/KEY 鎖的資源壓力,若果壓力比較大,鎖管理器請求一個  PAG 鎖來代替。
     頁級鎖減小了鎖的開銷,增進了查詢的性能,但它阻塞了該頁面上全部行的訪問從而損害了數據庫的併發性。
 
(4)區級鎖
     這種鎖在區(一組連續 8 個數據或索引頁面)上維護而且標識爲 EXT 鎖。
     如,這種鎖用於在一個表上執行 ALTER INDEX REBUILD 命令,而且該表從現有的區移動到新的區時。在這期間,區的完整性用 EXT 鎖來保護。
 
(5)堆或 B- 樹鎖
     堆或 B- 樹鎖用於描述這兩種對象(堆 和 B- 樹)被加鎖的狀況。這意味在一個無序的堆、沒有彙集索引的表上的鎖,或者一個 B- 樹對象上的鎖,一般指分區上的鎖。由於分區被跨多個文件組存儲,每一個都有本身的數據分配定義。它的操做相似於表級鎖,可是是在分區而不是表上進行。
 
(6)表級別
     這是表上最高級別的鎖。在一個表上的表級鎖保留了對整個表及其全部索引的訪問。
     在執行一個查詢時,鎖管理器若果肯定獲取行級鎖或是頁級鎖的資源壓力較高,這時會直接爲查詢獲取一個表級鎖。
 
(7)數據庫級鎖
     當應用程序創建一個數據庫鏈接時,鎖管理器分配一個數據庫共享鎖給對應的 SPID。這阻止用戶意外地在其餘用戶鏈接時卸掉或者恢復數據庫。
 
     鎖級別不須要由用戶或數據庫管理員指定,鎖管理器會自動肯定。在訪問少許行時,它通常首先行級鎖和鍵級鎖以提升併發性。若多個行級鎖的開銷變得很高時,鎖管理器會自動選擇合適的較高級別的鎖。
 
二、鎖模式
     根據所請求的類型,SQL Server 在鎖定資源時使用不一樣的鎖模式,下面是注意的三種模式:
  • 共享(S)
  • 更新(U)
  • 排他(X)
 
(1)共享(S)模式
     共享模式只用於只讀查詢。它不會阻止其餘只讀查詢同時訪問數據,由於查詢不會破壞數據完整性。可是會阻止數據上的併發修改。
S 鎖在數據上維持直到數據讀出。默認狀況下,SELECT 語句獲取的 S 鎖在數據讀出以後會當即釋放。
 
(2)更新(U)模式
     U 鎖與 S 鎖不一樣,U 鎖目的在於修改,所以爲了維護數據完整性,在數據上不容許超過一個 U 鎖。U 鎖與 UPDATE 語句有關。
     UPDATE 操做實際上有兩個中間步驟:
  1. 讀取須要修改的數據(加 U 鎖);
  2. 修改數據(加 X 鎖)。
 
     在這兩個步驟中,會使用不一樣的鎖模式以最大化併發性。第一步驟中,獲取數據上一個 U 鎖,第二步,U 鎖被轉換爲一個 X 排他鎖以進行修改。若不須要修改,U 鎖會被釋放,也就是說它不會被保存到事務結束。
 
     既然爲了提升併發性,爲何第一步獲取的是 U 鎖,而不是 S 鎖?
     這裏說說第一步中用 S 鎖代替 U 鎖的缺點。
     如有兩個鏈接同時執行一個事務,事務中是對同一條數據進行更新。兩個事務先都使用 S 鎖讀取須要修改的數據,而後在請求一個 X 鎖進行修改。當第一個事務試圖將 S 鎖轉換爲 X 鎖時,它被第二個事務保持的 S 鎖阻塞。一樣如此,第二個事務試圖將 S 鎖轉換爲 X 鎖時,也會被第一個事務的 S 鎖阻塞。這樣致使循環阻塞,也就發送了死鎖。
 
     爲了不這種狀況,UPDATE 語句在第一個中間階段使用 U 鎖代替 S 鎖。U 鎖不容許在相同的資源上使用另外一個 U 鎖,這樣強制第二個併發的 UPDATE 語句必須等待第一個 UPDATE 語句完成才執行。
 
(3)排他(X)模式
     X 鎖提供用於數據操做查詢,如 INSERT、UPDATE 和 DELETE 在數據庫資源上修改的排他能力。它阻止其餘事務訪問修改之下的資源。INSERT 和 DELETE 在執行開始獲取 X 鎖, UPDATE 語句在被修改的數據讀出以後轉換爲 X 鎖。在事務中授予的 X 鎖會保持到事務結束。
     X 鎖有兩個目的:
  • 阻止其餘事務訪問修改之下的資源,這樣他們能夠看到修改以前或以後的值,但不能是正在修改的值;
  • 在須要時容許事務修改資源以安全地回滾到修改以前的原始值,由於沒有其餘事務被容許同時修改資源。
 
 
三種之間的區別和聯繫:
     S 鎖 和 U 鎖 執行期很短,默認狀況下在讀出數據後會當即釋放;
     U 鎖 和 X 鎖 具備獨佔能力;
     X 鎖 執行期是在整個事務階段。
     
     SELECT 使用 S 鎖;INSERT 和 DELETE 使用 X 鎖;UPDATE 使用兩階段鎖,讀取階段爲 U 鎖,修改階段爲 X 鎖。
 
     使用 UPDATE 更新某一數據時,在掃描階段先對數據使用 U 鎖,若不知足當即退出,知足條件才轉換爲 X 鎖進入修改階段。
     
     思考:爲何 UPDATE 使用兩階段鎖,而 DELETE 直接使用 X 鎖 ?
 
 
3、隔離級別(ISOLATION)
 
     SQL Server 有這幾種隔離級別:
  • 未提交讀(READ UNCOMMITTED)
  • 已提交讀(READ COMMITTED)
  • 可重複讀(REPEATABLE READ)
  • 可序列化(SERIALIZABLE)
 
     其餘兩種隔離級別提供行版本控制(row versioning)。這種行的額外版本容許讀查詢訪問數據而不須要獲取鎖:
  • 已提交讀快照(已提交讀隔離的一部分)
  • 快照
 
(1)未提交讀
     未提交讀 是隔離級別中最低級的。它容許 SELECT 語句讀取數據而不須要請求 S 鎖,這樣它就不會被 X 阻塞,也不會阻塞 X 鎖。這樣就容許 SELECT 語句讀取正在修改(包括新增和刪除)的數據。這種讀出數據的方式被稱爲 髒讀
 
     有兩種方式來設置:
     使用 SET 語句配置數據庫鏈接的隔離級別:
     SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
 
    使用 NOLOCK 鎖提示在查詢上設置隔離級別:
     SELECT * FROM Products WITH (NOLOCK);
 
     注:髒讀 可能形成不可預知的狀況。由於讀取數據時沒加鎖,索引可能分離,這致使查詢返回的數據中多出或丟失行。
     
(2)已提交讀
     已提交讀 可避免出現髒讀的狀況,這意味着該隔離級別指示 SELECT 語句會請求 S 鎖。這也是數據庫的默認隔離級別。     
     可是,S 鎖不會被保留到事務結束,這樣可能形成 不可重複讀 或 幻讀 的問題。
     
     能夠經過開啓數據庫選項 READ_COMMITTED_SNAPSHOT 來啓用 已提交讀 隔離級別。當看起這選項時,數據操做事務會使用行版本控制。這會給 tempdb 帶來額外的負載,在事務未提交時被修改的行的前一個版本將保存在該數據庫裏。這使得其餘事務能夠讀訪問數據而不須要在數據上加鎖,可能會改善查詢的速度和效率(由於查詢數據不須要加 S 鎖)。
 
(3)可重複讀
     可重複讀隔離級別使得一條 SELECT 語句保持它的 S 鎖直到事務結束,這樣阻止了其餘事務在這段時間內修改該數據。
     可重複讀,某事務在執行期間可反覆讀取未被其餘事務修改的數據的能力。
 
     S 鎖容許數據獲取 U 鎖,可是會阻止 U 鎖轉換爲 X 鎖。這樣會有一種現象發生:
     A 事務查詢某數據,B 事務修改該數據。先執行 A 事務,在 A 事務結束以前執行 B 事務,該數據在 A 事務中加了 S 鎖,在 B 事務中加了 U 鎖。此時 B 事務 UPDATE 要修改數據,要轉換爲 X 鎖,這時 A 事務還未退出,阻止 B 事務中數據由 U 鎖轉換爲 X 鎖。事務 A 稍後更數據,試圖獲取 U 鎖,這樣就進入死循環。
     解決方法:在執行 SELECT 語句時使用 UPDLOCK 鎖提示請求一個 U 鎖,如:
     SELECT * FROM Products WITH (UPDLOCK);
 
(4)可序列化
     這是這幾張隔離級別中最高的隔離級別。可序列化不只在訪問的行上獲取一個鎖,並且還獲取在按照請求的數據級順序的下一行上的範圍鎖。這阻止了在第一個事務所操做的數據中的週期另外一事務增長行,避免第一個事務在其訪問內的數據集查找新建的行。也就避免了 幻讀(在事務內的數據集中查找新的行)。
     可序列化 隔離級別不只和 可重複讀 隔離級別同樣保留 S 鎖直到事務結束,並且還經過保持範圍鎖阻止數據集(或更多)中新建行。這可能很大的損害了數據庫並行性,全部應該避免該隔離級別。
 
(5)快照(Snapshot)
     快照隔離級別試圖在打算修改的數據上使用一個排他鎖。若數據已加鎖,快照事務將失敗。它提供了事務級讀一致性。
 
 
數據庫隔離級別:

 
隔離級別與併發性能的關係:
 
 
設置隔離級別原則
     優先將數據庫系統的隔離級別設置爲 ReadCommitted(MSSQL 默認級別),它能夠避免髒讀,而且有較好的併發性。
 
 
4、多個事務併發產生的問題
 
  • 第一類丟失更新:撤銷一個事務時,把其餘事務已提交的更新數據覆蓋
  • 髒讀:一個事務讀到另外一個事務未提交的更新數據
  • 虛讀:一個事務讀到另外一個事務已提交的新插入的數據
  • 不可重複讀:一個事務讀到另外一個事務已提交更新的數據
  • 第二類丟失更新:一個事務覆蓋另外一個事務已提交更新的數據(不可重複讀的特例)
隔離級別與併發性能的關係:
 
設置隔離級別原則
     優先將數據庫系統的隔離級別設置爲 ReadCommitted(MSSQL 默認級別),它能夠避免髒讀,而且有較好的併發性。
 
 
4、多個事務併發產生的問題
 
  • 第一類丟失更新:撤銷一個事務時,把其餘事務已提交的更新數據覆蓋
  • 髒讀:一個事務讀到另外一個事務未提交的更新數據
  • 虛讀:一個事務讀到另外一個事務已提交的新插入的數據
  • 不可重複讀:一個事務讀到另外一個事務已提交更新的數據
  • 第二類丟失更新:一個事務覆蓋另外一個事務已提交更新的數據(不可重複讀的特例)
 
參考書籍:
書籍:《SQL Server 2008 查詢性能優化》
相關文章
相關標籤/搜索