1. 程式人生 > >SQL Server 阻塞 死鎖說明與分析

SQL Server 阻塞 死鎖說明與分析

《SQL Server 2008 查詢效能優化》
一、加鎖(locking)、阻塞(blocking)、死鎖(deadlock)定義      加鎖:用於管理多個連線的程序。當連線需要訪問一塊資料時,在這些資料上放置某種型別的鎖。      阻塞:指一個連線需要訪問一塊資料時,必須等待另一個連線的鎖解除。      死鎖:指兩個連線形成被稱為"僵局"的形式,它們互相等待對方的鎖解除。      在 SQL Server 中,每個連線都可以看作是一個單獨的會話。 二、資料庫鎖 1、鎖粒度      為了改善併發性,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。這阻止使用者意外地在其他使用者連線時卸掉或者恢復資料庫。      鎖級別不需要由使用者或資料庫管理員指定,鎖管理器會自動確定。在訪問少量行時,它一般首先行級鎖和鍵級鎖以提高併發性。若多個行級鎖的開銷變得很高時,鎖管理器會自動選擇合適的較高級別的鎖。 2、鎖模式      根據所請求的型別,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 鎖 ? 三、隔離級別(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 預設級別),它可以避免髒讀,並且有較好的併發性。 四、多個事務併發產生的問題
  • 第一類丟失更新:撤銷一個事務時,把其他事務已提交的更新資料覆蓋
  • 髒讀:一個事務讀到另一個事務未提交的更新資料
  • 虛讀:一個事務讀到另一個事務已提交的新插入的資料
  • 不可重複讀:一個事務讀到另一個事務已提交更新的資料
  • 第二類丟失更新:一個事務覆蓋另一個事務已提交更新的資料(不可重複讀的特例)
隔離級別與併發效能的關係: 設定隔離級別原則      優先將資料庫系統的隔離級別設定為 ReadCommitted(MSSQL 預設級別),它可以避免髒讀,並且有較好的併發性。 四、多個事務併發產生的問題
  • 第一類丟失更新:撤銷一個事務時,把其他事務已提交的更新資料覆蓋
  • 髒讀:一個事務讀到另一個事務未提交的更新資料
  • 虛讀:一個事務讀到另一個事務已提交的新插入的資料
  • 不可重複讀:一個事務讀到另一個事務已提交更新的資料
  • 第二類丟失更新:一個事務覆蓋另一個事務已提交更新的資料(不可重複讀的特例)