1. 程式人生 > 其它 >SQL Server 鎖升級(Lock Escalations)

SQL Server 鎖升級(Lock Escalations)

在今天的文章裡,我想談下SQL Server裡鎖升級(Lock Escalations)。鎖升級是SQL Server使用的優化技術,用來控制在SQL Server鎖管理裡把持鎖的數量。我們首先用SQL Server裡所謂的鎖層級(Lock Hierarchy)開始,因為那是在像SQL Server的關係資料庫裡,為什麼有鎖升級概念存在的原因。

鎖層級(Lock Hierarchy)

下圖展示了SQL Server使用的鎖層級:

從圖裡可以看到,鎖層級開始於資料庫層級,向下至行層級。在資料庫本身層級,你一直有一個共享鎖(Shared Lock (S))。當你的查詢連線到一個數據庫(例如USE MyDatabase

),共享鎖會阻止資料庫刪除,或者在那個資料庫上還原備份。當你進行一個操作時,在資料庫層級下,在表上,在頁上,在記錄上都會有鎖。

當你執行一個SELECT語句,在表和頁上會有一個意向共享鎖(Intent Shared Lock (IS)),在記錄本身上有共享鎖(Shared Lock (S))。當你進行資料修改語句(INSERT,UPDATE,DELETE),在表和頁上會有一個意向排它或更新鎖(Intent Exclusive or Update Lock (IX or IU)),在改變的記錄上有排它或更新鎖(Exclusive or Update Lock (X or U))。當多個執行緒嘗試在鎖層級裡併發獲取鎖時,SQL Server會一直獲取從頭到腳的鎖來阻止所謂的

競爭危害。當你對錶進行20000條記錄的刪除操作時,現在想象下這個鎖層級會是什麼樣的?我們來假定記錄是400 bytes長,這就意味這在8kb的頁裡剛好有20條記錄:

在資料庫上你有1個共享鎖(S),在表上有1個意向排它鎖(IX),在頁上有1000個意向排它鎖(IX)(20000條記錄散佈在1000個頁上),最後在記錄本身你有20000個排它鎖(X)。對於DELETE操作總計你獲取了21002個鎖。在SQL Server裡每個鎖需要96 bytes的記憶體,因此對這個簡單的查詢需要1.9MB的鎖。當你並行執行多個查詢時,這個不會無限擴充套件。因此,SQL Server現在用所謂的鎖升級(Lock Escalation)實現。

鎖升級(Lock Escalations)

在你的鎖層級裡一旦有超過5000個鎖,SQL Server會升級這麼多的精細粒度(fine-granularity)的鎖為簡單的粗粒度(coarse-granularity)的鎖。預設情況下,SQL Server“總是”升級到表層級。這意味著你的鎖層級從剛才例子的樣子,在鎖升級成功執行後,變成如下的樣子:

如你所見,在表本身上你只有一個大鎖。在DELETE操作的情況下,在表層級你有一個排它鎖(X)。這會以負面傷及你資料庫的併發。在表層級把持一個排它鎖(X)意味者沒有其他回話可以訪問那個表——每個其它查詢會阻塞。當你在可重讀隔離級別(Repeatable Read Isolation Level)執行你的SELECT語句,你也在把持你的共享鎖(S)直到事務結束,這也就是說只要你讀超過5000條記錄就會發生鎖升級。這裡的結果是一個共享鎖(S)在表本身!你的表只是暫時只讀,因為在表上每個其它資料修改都會阻塞!

這裡還有個誤解——SQL Server會鎖升級從行層級到頁層級,最後到表層級。錯了!在SQL Server裡沒有這樣的程式碼路徑存在!預設情況下,SQL Server總是會直接升級到表層級。到頁層級的升級策略不存在。如果你的表被分割槽了(只針對企業版本的SQL Server),那樣的話,你可以配置升級到分割槽層級。但這裡你必須非常仔細測試你的資料訪問模式,因為鎖升級到分割槽層級會引起死鎖。因此這個選項預設是沒啟用的。

自SQL Server 2008開始,你可以控制SQL Server如何進行鎖升級——通過ALTER TABLE語句和LOCK_ESCALTATION屬性。有3個可用選項:

  • TABLE
  • AUTO
  • DISABLE
 -- Controllling Lock Escalation
ALTER TABLE Person.Person
SET
(
     LOCK_ESCALATION = AUTO -- or TABLE or DISABLE
)
GO

預設選項是TABLE,意味著SQL Server總是執行鎖升級到表層級——即使這個表已被分割槽。如果你的表已被分割槽,你想設定分割槽層級的鎖升級(因為你已經測試了你的資料訪問模式,用它你不會引起死鎖),那麼你可以修改選項為AUTOAUTO意味著你的鎖升級會執行到分割槽層級,如果表被分割槽的話,否則就到表層級。使用DISABLE選項你可以完全禁用那個表的鎖升級。但是禁用鎖升級並不是最好的選項,因為SQL Server的鎖管理器會消耗大量的記憶體,如果你對你的查詢和索引設計不深思熟慮的話。

減少鎖升級

  • 優先使用行版本控制跟蹤標誌1211。
  • DBCC TRACEON 1211, -1
  • 跟蹤標誌1224
  • 加大閥值,減少鎖升級的可能。In many cases, it is desirable to control the escalation threshold at the object level
sp_configure 'locks', 10000;
GO
RECONFIGURE;
GO

在大多數情況下,資料庫引擎使用預設的鎖定和鎖升級設定進行操作時提供的效能最佳。如果資料庫引擎例項生成大量鎖並且頻繁進行鎖升級,請考慮通過下列方法減少鎖定:

  • 對於讀取操作,使用不會生成共享鎖的隔離級別。
  • 當 READ_COMMITTED_SNAPSHOT 資料庫選項為 ON 時,使用 READ COMMITTED 隔離級別。
  • 使用 SNAPSHOT 隔離級別。
  • 使用 READ UNCOMMITTED 隔離級別。此隔離級別只能用於能對髒讀進行操作的系統。
  • 使用 PAGLOCK 或 TABLOCK 表提示,使資料庫引擎使用頁、堆或索引鎖而不是行鎖。但是,使用此選項增加了使用者阻止其他使用者嘗試訪問相同資料的問題,對於併發使用者較多的系統,不應使用此選項。

還可以使用跟蹤標誌 1211 和 1224 來禁用所有或某些鎖升級。

監控鎖升級

可以使用 SQL Server Profiler Lock:Escalation 事件監視鎖升級,請參閱。

鎖升級的具體物件:

每個升級事件主要在單個 Transact-SQL 語句級別上操作。當事件啟動時,只要活動語句滿足升級閾值的要求,資料庫引擎就會嘗試升級當前事務在活動語句所引用的任何表中持有的所有鎖。如果升級事件 在語句訪問表之前啟動,則不會嘗試升級該表上的鎖。如果鎖升級成功,只要表被當前語句引用並且包括在升級事件中,上一個語句中事務獲取的、在事件啟動時仍 被持有的鎖都將被升級。

例如,假定某個會話執行下列操作:

  • 開始一個事務。
  • 更新 TableA。這將在 TableA 中生成排他行鎖,直到事務完成後才會釋放該鎖。
  • 更新 TableB。這將在 TableB 中生成排他行鎖,直到事務完成後才會釋放該鎖。
  • 執行聯接 TableATableC 的 SELECT 語句。查詢執行計劃要求先從 TableA 中檢索行,然後才從 TableC 中檢索的行。
  • SELECT 語句在從 TableA 中檢索行時(此時還沒有訪問 TableC)觸發鎖升級。

如果鎖升級成功,只有會話在 TableA 中持有的鎖才會升級。這包括來自 SELECT 語句的共享鎖和來自上一個 UPDATE 語句的排他鎖。由於決定是否應進行鎖升級時只考慮會話在 TableA 上為 SELECT 語句獲取的鎖,所以一旦升級成功,會話在 TableA 上持有的所有鎖都將被升級到該表上的排他鎖,而 TableA 上的所有其他較低粒度的鎖(包括意向鎖)都將被釋放。

不會嘗試升級 TableB 上的鎖,因為 SELECT 語句中沒有 TableB 的活動引用。同樣,也不會嘗試升級 TableC 上尚未升級的鎖,因為發生升級時尚未訪問該表。

小結

在SQL Server裡鎖升級基本是個噩夢。你如何才能從表裡刪除5000行記錄而不產生鎖升級?你可以臨時禁用鎖升級,但這裡你要非常仔細。另外一個方法(我推薦的)是讓你的DELETE/UPDATE語句在一個迴圈裡,作為不同,獨立的事務:DELETE/UPDATE少於5000行記錄,這樣的話你可以阻止鎖升級。這樣做的好處,你龐大的事務會分解為多個小事務,但也會讓你的事務日誌更多,帶來自動增長問題。

感謝關注!

參考文章:

https://www.sqlpassion.at/archive/2014/02/25/lock-escalations/