1. 程式人生 > >SQLServer鎖機制淺

SQLServer鎖機制淺

SQL SERVER 鎖機制淺讀

內容為本人根據當前正在讀的《SQL Server效能調優實戰》(陳暢亮 吳一晴 著 機械工業出版社)和前輩分享的PPT,通讀後的總結與彙總。

  • 鎖及事務機制的存在,便於關係型資料庫實現它的四個基本特性。
  • 當兩個不同的程序試圖同時修改同一份資料時,資料庫管理系統(DBMS)負責解決他們之間潛在的衝突。
  • 理論上所有的事務之間應該是完全隔離的。但是實際上,要實現完全隔離成本實在是太高(必須是序列化的隔離等級才能完全隔離)。所以,SQLServer通過鎖,就像十字路口的紅綠燈一樣,告訴所有併發的連結,在同一時刻上,哪些資源可以讀取,哪些資源可以修改。當一個事務需要訪問的資源加了其所不相容的鎖,SQLServer會阻塞當前事務來達成所謂的隔離性,直到其所請求資源上的鎖被釋放。
  • SQL Server在隔離和併發之間選擇了Read Commited作為資料庫的預設隔離級別

ACID

  • 原子性(Atomicity):既不可再分,一個事務,要麼全部成功,要麼全部失敗。一個語句,要麼執行,要麼不執行。
  • 一致性(Consistency):事務執行前後,不論成敗,資料庫的完整性約束沒有被破壞。(唯一約束,外來鍵約束,check約束和觸發器設定等)。這一點是由SQLServer進行保證的。
  • 隔離性(Isolation):事務是互不干擾的,一個事務不可能看到其它事務執行時,中間某一時刻的資料。
  • 永續性(Durability):事務結束前(大部分書稱之為‘成功後’),對資料的所有改動都已經從記憶體轉為外部儲存器(物理儲存器)上。其實應該說是對資料的改動後的結果是可持久的。這樣子系統任何時候癱瘓,已提交事務所進行的更新不會丟失。

鎖、事務要解決的問題

  • 髒讀:在某個會話中,讀取了還未提交的事務中修改或更新的資料。
  • 不可重複讀:在同一事務中,前後兩次讀取相同的資料,結果卻不一樣。
  • 幻讀:當某個事務查詢或更新某個範圍內的資料時,另一個事務在這個範圍內插入了某條資料,會導致當前事務沒有將這條新增的資料返回或更新。
  • 重複讀:當某個查詢以索引掃描的方式讀取資料時,另一個事務更新了索引資料,並導致資料向後轉移了位置。此時會把已經讀取過但被挪動了位置的資料重新讀取出來。
  • 丟失更新:事務T1讀取了資料,並執行了一些操作,然後更新資料。事務T2也做了相同的事,則T1和T2更新資料資料時可能會覆蓋對方的更新,從而引起錯誤。

併發控制的主要方法是通過鎖,在一段時間內禁止使用者做某些操作以避免產生資料不一致。

丟失更新例子:A、B兩個使用者讀同一資料並進行修改,其中一個使用者的修改結果破壞了另一個修改的結果,比如訂票系統。

PS:這個‘丟失更新’問題,是我在前輩分享的ppt中看到的,但在我正在讀的《SQL Server效能調優實戰》(陳暢亮 吳一晴 著 機械工業出版社)中未找到這一條,暫時不是很懂。 猜測一下,所謂的更新覆蓋,應該是類似併發時,A使用者執行操作導致剩餘數量為3,B使用者執行操作導致剩餘數量為2,然而結果剩餘數量為3的把剩餘數量為2的更新覆蓋了,就會導致資料出錯。 我這邊開發這種情況時,根據前輩的建議,一直都是儘量在SQL中執行加減操作,而非程式中加減後,將剩餘數量入庫。所以就避免了丟失更新問題。

隔離級別

  • Read Uncommitted[ˌʌnkəˈmɪtɪd]:此隔離級別會有髒讀、不可重複讀和幻讀的情況發生,但併發效能最佳,因為它不會為讀操作新增共享鎖。
  • Read Committed[kəˈmɪtɪd]:此隔離級別會有不可重複讀、幻讀和重複讀的情況發生,它的併發性比Read Uncommitted差,是SQL Server預設的隔離級別。
  • Repeatable[rɪˈpi:təbl]:此隔離級別會有幻讀情況,它與Read Committed的區別在於,共享鎖持有的時間會比較長,需要等待到事務提交或回滾後才釋放共享鎖。由於共享鎖持有的時間長了,阻塞相對也會多一些,特別是對於更新操作會有較多的阻塞。
  • Serializable[sɪərɪrlaɪ’zəbl]:此隔離級別不會有以上提到的四種問題出現,但其代價是最高的。該隔離級別鎖引入了**Range Lock(區間鎖)**的概念。區間鎖是指在進行查詢、更新、刪除、插入操作時,對更新資料中某個去與內的資料加鎖。因此其併發非常有限。
  • Snapshot[ˈsnæpʃɑ:t]:上面四個隔離級別是以“悲觀鎖”的方式實現的,而Snapshot是以“樂觀鎖”的方式實現的。資料庫會在Tempdb中建立與之相應的副本,從而避免髒讀和不可重複讀的問題。

從上面可以瞭解: 要解決不可重複讀和重複讀問題,需要將隔離級別升到Repeatable,這個級別的事務會保持共享鎖,直到事務回滾或提交。 要解決幻讀問題,則需要將隔離級別提升到Serializable,這個隔離級別為事務中更新的資料新增區間鎖。

PS:在我當前開發的電商專案中,我們的所有查詢SQL都要新增WITH(NOLOCK)來查詢,應該是為了提高併發效能,允許其進行髒讀。手動將預設隔離級別降級為Read Uncommitted。

鎖模式

  • 共享鎖(S鎖):發生在資料查詢之前。用於讀取資源所加的鎖,允許多個事務對同一物件使用相同的共享鎖,擁有共享鎖的資源不能被修改。共享鎖預設情況下是讀取了資源馬上釋放。不會等到事務提交或回滾。
  • 排他鎖(X鎖):發生在資料更新之前。用於資料修改,排他鎖是一個獨佔鎖,和其他任何鎖都不相容,包括其它排他鎖。當資源加了排他鎖,其它請求讀取或修改這個資源的的事務都會被阻塞。
  • 更新鎖(U鎖):發生在更新語句中。用於更新資料,當查詢的資料不是被更新物件時,與S鎖一樣處理。若確認是被更新的物件,則將U鎖轉換為X鎖。SQL Server通過U鎖來避免死鎖問題。因為S鎖和S鎖是相容的,U鎖和S鎖相容的,來使得更新查詢時不影響資料查詢,而U鎖和U鎖之間並不相容從而減少了死鎖的可能性。
  • 意向鎖(IS IX IU SIX SIU UIX):發生在較低粒度級別的資源獲取鎖之前,表示將對該資源下低粒度的資源新增對應的鎖。意向共享鎖(IS鎖)、意向排他鎖(IX鎖)、意向更新鎖(IU鎖)、共享意向排他鎖(SIX鎖)、共享意向更新鎖(SIU鎖)、更新意向排他鎖(UIX鎖)。
  • 快照(SNOPSHOT):通過在tempDB中建立一個額外的副本來避免髒讀、不可重複度,會給tempDB造成負擔。
  • 鍵範圍鎖(KEY-RANGE):在使用可序列化事務隔離級別時,對於Transact-SQL語句讀取的記錄集,鍵範圍鎖可以隱式保護該記錄集中包含的行範圍。可序列化隔離級別要求每當在事務期間執行任一查詢時,該查詢都必須獲取相同的行集。鍵範圍鎖可防止其他事務插入其鍵值位於可序列化事務讀取的鍵值範圍內的新行,從而確保滿足此要求。 鍵範圍鎖可防止幻讀。通過保護行之間的鍵範圍,它還可以防止對事務訪問的記錄集進行幻插入。 鍵範圍鎖放置在索引上,指定開始鍵值和結束鍵值。此鎖將阻止任何要插入、更新或刪除任何帶有該範圍內的鍵值的行的嘗試,因為這些操作會首 先獲取索引上的鎖。例如,可序列化事務可能發出了一個 SELECT 語句,以讀取其鍵值介於 ‘AAA’ 與 ‘CZZ’ 之間的所有行。從 ‘AAA’ 到 ‘CZZ’ 範圍內的鍵值上的鍵範圍鎖可阻止其他事務插入帶有該範圍內的鍵值(例如 ‘ADG’、‘BBD’ 或 ‘CAL’)的行。
  • 架構鎖:SQL Server使用架構鎖來保持表結構的完整性。不像其他提供資料隔離的鎖型別,架構鎖提供事務中對資料庫物件如表、檢視、索引的schema隔離。
  • 大容量更新鎖:在向表進行大容量資料複製且指定了 TABLOCK 提示時使用。

PS:意向鎖更像是一個指示器。因為資源是有層次的,鎖也是有層級結構的。行、頁、表。簡單來說,當一個粒度比較低的資源被鎖定時,會在其父資源加上意向鎖,告訴其它查詢這個資源的某一部分已經上鎖,不必一行行去找是否有被鎖定的低粒度資源。 PS:快照(SNOPSHOT)這個我看到有歸類到隔離級別上,有歸類到鎖模式上,至於它是否因為本質上特殊,可以達到兩種效果而導致大家對其分界認知有所模糊。我尚未用過,尚不明確,留待後續學習。 PS:快照及後面三個鎖模式鍵範圍鎖、架構鎖、大容量更新鎖,在《SQL Server效能調優實戰》書中未標明這四種鎖模式,又感覺與鎖粒度頗有關係的樣子。尚不明確,留待後續學習。

鎖粒度

  • 行鎖:是SQL Server資料級別中粒度最小的鎖級別,行鎖可針對資料中的某一行新增一個對應型別的鎖。行鎖根據表是否存在狙擊索引粉為鍵值鎖和行標識鎖(RID)。
  • 頁鎖:針對某個資料頁新增的鎖。在T-SQL語句中,如果查詢使用了頁鎖,則不會再使用相同型別的行鎖,如果使用了行鎖,也不會再使用相同型別的頁鎖。對資料頁加鎖後,將無法再對資料頁使用與之不相容的鎖。
  • 表鎖:在整個表中新增鎖。若表被鎖了,其中的資料頁及資料行都無法加上預製不相容的鎖。

所謂所粒度,從本質上說就是,為了給事務提供完全的隔離和序列化,作為查詢或更新的一部分被鎖定的資料的總量(的大小)。Lock Manager需要在資源的併發訪問與維護大量低級別鎖的管理開銷之間取得平衡。比如,鎖的粒度越小,能夠同時訪問同一張表的併發使用者的數量就越大,不過維護這些鎖的管理開銷也越大。鎖的粒度越大,管理鎖需要的開銷就越少,而併發性也降低了。下圖說明了鎖的大小與併發性之間的權衡取捨。 在這裡插入圖片描述

鎖升級

鎖升級是將許多較細粒度的鎖轉換成數量更少的較粗粒度的鎖的過程,這樣可以減少系統開銷,但卻增加了併發爭用的可能性。

當 SQL Server 資料庫引擎獲取低級別的鎖時,它還將在包含更低級別物件的物件上放置意向鎖: 1、當鎖定行或索引鍵範圍時,資料庫引擎將在包含這些行或鍵的頁上放置意向鎖。 2、當鎖定頁時,資料庫引擎將在包含這些頁的更高級別的物件上放置意向鎖。除了物件上的意向鎖以外,以下物件上還需要意向頁鎖: 非聚集索引的葉級頁 聚集索引的資料頁 堆資料頁

鎖升級的閾值: 單個 Transact-SQL 語句在單個無分割槽表或索引上獲得至少 5,000 個鎖。 單個 Transact-SQL 語句在已分割槽表的單個分割槽上獲得至少 5,000 個鎖,並且 ALTER TABLE SET LOCK_ESCALATION 選項設為 AUTO。

資料庫引擎例項中的鎖的數量超出了記憶體或配置閾值或持有鎖的時間過長時,會可能引發鎖升級。

TIPS:資料庫引擎不會將行鎖或鍵範圍鎖升級到頁鎖,而是將它們直接升級到表鎖。同樣,頁鎖始終升級到表鎖。

簡單的思維導圖

這裡寫圖片描述

createtime:2018-11-14