全面瞭解MSSQL鎖機制以及應用

1、鎖概念及鎖應用

鎖的概念

當用戶併發對數據庫進行操做時會帶來數據不一致的問題,例如:sql

  • 更新丟失(兩個用戶讀同一個數據並進行修改,一個用戶破壞了另外一個用戶的修改結果)
  • 髒讀(讀出還沒有提交事務的數據,產生了髒讀)
  • 不可重複讀(用戶屢次讀取的數據結果不一致)

因此在出現用戶併發操做的時候,應該提供鎖,就是在一段時間內禁止用戶作某些操做以免產生數據不一致。數據庫

鎖的分類

  1. 數據庫的讀寫的角度來分:分爲獨佔鎖(即排它鎖),共享鎖和更新鎖
    • 共享鎖 (S):安全

      共享 (S) 鎖容許併發事務讀取 (SELECT) 一個資源。資源上存在共享 (S) 鎖時,任何其它事務都不能修改數據。一旦已經讀取數據,便當即釋放資源上的共享 (S) 鎖,除非將事務隔離級別設置爲可重複讀或更高級別,或者在事務生存週期內用鎖定提示保留共享 (S) 鎖bash

    • 更新鎖網絡

      更新 (U) 鎖能夠防止一般形式的死鎖。通常更新模式由一個事務組成,此事務讀取記錄,獲取資源(頁或行)的共享 (S) 鎖,而後修改行,此操做要求鎖轉換爲排它 (X) 鎖。若是兩個事務得到了資源上的共享模式鎖,而後試圖同時更新數據,則一個事務嘗試將鎖轉換爲排它 (X) 鎖。共享模式到排它鎖的轉換必須等待一段時間,由於一個事務的排它鎖與其它事務的共享模式鎖不兼容;發生鎖等待。第二個事務試圖獲取排它 (X) 鎖以進行更新。因爲兩個事務都要轉換爲排它 (X) 鎖,而且每一個事務都等待另外一個事務釋放共享模式鎖,所以發生死鎖。session

      若要避免這種潛在的死鎖問題,請使用更新 (U) 鎖。一次只有一個事務能夠得到資源的更新 (U) 鎖。若是事務修改資源,則更新 (U) 鎖轉換爲排它 (X) 鎖。不然,鎖轉換爲共享鎖。併發

    • 排它鎖工具

      排它 (X) 鎖能夠防止併發事務對資源進行訪問。其它事務不能讀取或修改排它 (X) 鎖鎖定的數據sqlserver

    • 意向鎖 意向鎖表示 SQL Server 須要在層次結構中的某些底層資源上獲取共享 (S) 鎖或排它 (X) 鎖。例如,放置在表級的共享意向鎖表示事務打算在表中的頁或行上放置共享 (S) 鎖。在表級設置意向鎖可防止另外一個事務隨後在包含那一頁的表上獲取排它 (X) 鎖。意向鎖能夠提升性能,由於 SQL Server 僅在表級檢查意向鎖來肯定事務是否能夠安全地獲取該表上的鎖。而無須檢查表中的每行或每頁上的鎖以肯定事務是否能夠鎖定整個表。性能

      意向鎖包括意向共享 (IS)、意向排它 (IX) 以及與意向排它共享 (SIX)

      意向共享 (IS) 經過在各資源上放置 S 鎖,代表事務的意向是讀取層次結構中的部分(而不是所有)底層資源。

      意向排它 (IX) 經過在各資源上放置 X 鎖,代表事務的意向是修改層次結構中的部分(而不是所有)底層資源。IX 是 IS 的超集。

      與意向排它共享 (SIX) 經過在各資源上放置 IX 鎖,代表事務的意向是讀取層次結構中的所有底層資源並修改部分(而不是所有)底層資源。容許頂層資源上的併發 IS 鎖。例如,表的 SIX 鎖在表上放置一個 SIX 鎖(容許併發 IS 鎖),在當前所修改頁上放置 IX 鎖(在已修改行上放置 X 鎖)。雖然每一個資源在一段時間內只能有一個 SIX 鎖,以防止其它事務對資源進行更新,可是其它事務能夠經過獲取表級的 IS 鎖來讀取層次結構中的底層資源

  2. 從程序的角度進行分:(也就是常常會提到的樂觀鎖和悲觀鎖)
    • 樂觀鎖:假設不會發生併發衝突,只在提交操做時檢查是否違反數據完整性,過後處理--(數據版本/時間戳)
    • 悲觀鎖:假定會發生併發衝突,屏蔽一切可能違反數據完整性的操做,事前處理--(使用數據庫的鎖機制)

鎖的粒度

鎖粒度是被封鎖目標的大小,封鎖粒度小則併發性高,但開銷大,封鎖粒度大則併發性低但開銷小,SQL Server支持的鎖粒度能夠分爲爲行、頁、鍵、鍵範圍、索引、表或數據庫獲取鎖

  • DATABASE
  • FILE
  • EXTENT
  • ALLOCATION_UNIT
  • TABLE
  • Heap or B-Tree (HOBT)
  • PAGE
  • Row ID (RID)
  • KEY
  • METADATA
  • APPLICATION

鎖在SQL中的應用

  1. HOLDLOCK: 在該表上保持共享鎖,直到整個事務結束,而不是在語句執行完當即釋放所添加的鎖。 
  2. NOLOCK:不添加共享鎖和排它鎖,當這個選項生效後,可能讀到未提交讀的數據或「髒數據」,這個選項僅僅應用於SELECT語句。 
  3. PAGLOCK:指定添加頁鎖(不然一般可能添加表鎖)。 
  4. READCOMMITTED用與運行在提交讀隔離級別的事務相同的鎖語義執行掃描。默認狀況下,SQL Server 2000 在此隔離級別上操做。
  5. READPAST: 跳過已經加鎖的數據行,這個選項將使事務讀取數據時跳過那些已經被其餘事務鎖定的數據行,而不是阻塞直到其餘事務釋放鎖,READPAST僅僅應用於READ COMMITTED隔離性級別下事務操做中的SELECT語句操做。  
  6. READUNCOMMITTED:等同於NOLOCK。  
  7. REPEATABLEREAD:設置事務爲可重複讀隔離性級別。 
  8. ROWLOCK:使用行級鎖,而不使用粒度更粗的頁級鎖和表級鎖。  
  9. SERIALIZABLE:用與運行在可串行讀隔離級別的事務相同的鎖語義執行掃描。等同於 HOLDLOCK。 
  10. TABLOCK:指定使用表級鎖,而不是使用行級或頁面級的鎖,SQL Server在該語句執行完後釋放這個鎖,而若是同時指定了HOLDLOCK,該鎖一直保持到這個事務結束。  
  11. TABLOCKX:指定在表上使用排它鎖,這個鎖能夠阻止其餘事務讀或更新這個表的數據,直到這個語句或整個事務結束。 
  12. UPDLOCK :指定在讀表中數據時設置更新 鎖(update lock)而不是設置共享鎖,該鎖一直保持到這個語句或整個事務結束,使用UPDLOCK的做用是容許用戶先讀取數據(並且不阻塞其餘用戶讀數據),而且保證在後來再更新數據時,這一段時間內這些數據沒有被其餘用戶修改。

總結,

粒度鎖:PAGLOCK, TABLOCK, TABLOCKX, ROWLOCK, NOLOCK

模式鎖:HOLDLOCK, UPDLOCK, XLOCK
複製代碼

處理死鎖

如何避免死鎖,最小化鎖競爭

  • 使用事務時,儘可能縮短事務的邏輯處理過程,及早提交或回滾事務,事務持有鎖的時間越短,鎖競爭發生的機會就越少;將不是事務所管理的工做單元鎖必需的命令移出事務;

  • 設置死鎖超時參數爲合理範圍,如:3分鐘-10分種;超過期間,自動放棄本次操做,避免進程懸掛;

  • 優化程序,檢查並避免死鎖現象出現;

  • 通常不要修改SQL SERVER事務的默認級別。不推薦強行加鎖

  • 將組成事務的語句做爲一個的單獨的批命令處理,以消除 BEGIN TRAN 和 COMMIT TRAN 語句之間的網絡延遲形成的沒必要要的延遲。

  • 考慮徹底地使用存儲過程編寫事務代碼。典型地,存儲過程比批命令運行更快。

  • 在遊標中儘可早地Commit更新。由於遊標處理比面向集合的處理慢得多,所以致使鎖被持有的時間更久。

  • 使用每一個進程所需的最低級別的鎖隔離。好比說,若是髒讀是可接受的而且不要求結果必須精確,那麼能夠考慮使用事務隔離級別0(Read Uncommitted),僅在絕對必要時才使用Repeatable Read or Serializable隔離級別。

  • 在 BEGIN TRAN 和 COMMIT TRAN 語句之間,毫不容許用戶交互,由於這樣作可能鎖被持有無限期的時間。

示例講解

初始化數據

新建一個數據庫dblock,並執行一下SQL

DROP TABLE dbo.LockTest
CREATE TABLE LockTest(ID INT IDENTITY,NAME CHAR(4000) DEFAULT 'name')
--插入6條數據,恰好3個數據頁
--4000字節 兩條數據就是一個數據頁
INSERT INTO dbo.LockTest DEFAULT VALUES
INSERT INTO dbo.LockTest DEFAULT VALUES
INSERT INTO dbo.LockTest DEFAULT VALUES
INSERT INTO dbo.LockTest DEFAULT VALUES
INSERT INTO dbo.LockTest DEFAULT VALUES
INSERT INTO dbo.LockTest DEFAULT VALUES

複製代碼

數據初始化狀態

分如下多種狀況進行,看看鎖是如何工做

0.SQL SERVER Profiler使用

這個工具它對SQL Server的監視能力能夠說是無所不能,以下圖咱們新建事件勾選兩項,分別是鎖的獲取和釋放:

  1. 首先咱們查看下,如剛開始初始化數據所寫一直,看一下在sqlserver中數據是如何分佈的,分別能夠看到表LockTest中有三個數據頁,後面還會繼續在監視中看到

  1. 瞭解基本的加鎖狀況

    • 經過查詢和更新/刪除,查看Profiler中的顯示狀況

      SELECT * FROM LockTest
      複製代碼

      產生了意向鎖(IS) , 鎖的粒度分別是 TABLE,PAGE 這裏就已經能夠看出來是3個數據頁了。先是Object 鎖 而後逐頁添加 Page 鎖 掃描一數據頁 釋放一個 IS鎖

    • 索引解決剛剛出現的查詢阻塞

      UPDATE SET NAME = '3' where ID = '1'
      複製代碼

      先是Object IX 鎖 page的 IU鎖 rid u鎖 掃描到指定須要更改的 page IX rid X 最後掃描完 再釋放 rid x page IX object IX。這條是須要注意鎖釋放的時間

    • 經過事務演示阻塞問題

      -- 會話1
      BEGIN TRAN
      UPDATE LockTest SET NAME='4' WHERE ID = '1'
      
      
      ROLLBACK TRAN
      
      -- 會話2
      
      SELECT * FROM LockTest WHERE ID=2
      
      複製代碼

      如圖所示,顯示會在78頁獲取IS鎖時候發生等待釋放X鎖而發生阻塞

      SELECT  l.request_session_id,
       DB_NAME(l.resource_database_id),OBJECT_NAME(p.object_id),
       l.resource_description,l.request_type,
       l.request_status,request_mode 
       FROM sys.dm_tran_locks AS l
       LEFT JOIN sys.partitions AS p
       ON l.resource_associated_entity_id=p.hobt_id
      複製代碼

      以上SQL 能夠顯示出 發生阻塞再等待

    • 利用索引跳過阻塞

      -- 建立索引
      CREATE INDEX idx_LockTest ON dbo.LockTest(ID)
      
      -- 顯示索引
      SELECT * FROM LockTest WITH(INDEX(idx_LockTest)) WHERE ID=2
      
      複製代碼

      能夠看出在執行ROLLBACK TRAN 前,未釋放X鎖的前提下SELECT經過索引直接查出告終果

      使用主鍵同樣能夠實現以上

      數據庫在建立主鍵同時,會自動創建一個惟一索引。若是這個表以前沒有彙集索引,同時創建主鍵時候沒有強制指定使用非彙集索引,則創建主鍵時候,同時創建一個惟一的彙集索引
      複製代碼
    • 經過SQL Server Profiler能夠對以上粒度鎖進行一一查看,更加深刻的瞭解每種粒度

鎖機制以及各類鎖出現的狀況

在使用SQL 語句的時候,常常會遇到在一個事務的中,解決不可重複讀或者是丟失更新的問題

通常解決辦法就是使用鎖和事物的聯合機制:

  1. 把select放在事務中, 不然select完成, 鎖就釋放了
  2. 要阻止另外一個select , 則要手工加鎖, select 默認是共享鎖, select之間的共享鎖是不衝突的, 因此, 若是隻是共享鎖, 即便鎖沒有釋放, 另外一個select同樣能夠下共享鎖, 從而select出數據
BEGIN TRAN  
SELECT * FROM Table WITH(UPDLOCK)   
--或者 SELECT * FROM Table WITH(TABLOCKX, READPAST) 具體狀況而定。  
UPDATE ....  
COMMIT TRAN  
複製代碼

全部Select加 With (NoLock)解決阻塞死鎖,在查詢語句中使用 NOLOCK 和 READPAST 處理一個數據庫死鎖的異常時候,其中一個建議就是使用 NOLOCK 或者 READPAST 。有關 NOLOCK 和 READPAST的一些技術知識點: 對於非銀行等嚴格要求事務的行業,搜索記錄中出現或者不出現某條記錄,都是在可容忍範圍內,因此碰到死鎖,應該首先考慮,咱們業務邏輯是否能容忍出現或者不出現某些記錄,而不是尋求對雙方都加鎖條件下如何解鎖的問題。 NOLOCK 和 READPAST 都是處理查詢、插入、刪除等操做時候,如何應對鎖住的數據記錄。可是這時候必定要注意NOLOCK 和 READPAST的侷限性,確認你的業務邏輯能夠容忍這些記錄的出現或者不出現: 簡單來講:

1.NOLOCK 可能把沒有提交事務的數據也顯示出來 2.READPAST 會把被鎖住的行不顯示出來

不使用 NOLOCK 和 READPAST ,在 Select 操做時候則有可能報錯誤:事務(進程 ID **)與另外一個進程被死鎖在 鎖 資源上,而且已被選做死鎖犧牲品。

SELECT * FROM Table WITH(NOLOCK) SELECT * FROM Table WITH(READPAST)

1.死鎖

以下圖能夠看到發生死鎖,咱們能夠經過上面所說的 設置優先級別和超時來控制發生鎖競爭的結果,分別會顯示「而且已被選做死鎖犧牲品」,「已超過了鎖請求超時時段」

2.插入鎖

"表鎖"鎖定對該表的Select、Update、Delete操做,但不影響對該表的Insert操做也不影響以主鍵Id爲條件的Select,因此Select若是不想等待就要在Select後加With(Nolock),但這樣會產生髒數據就是其餘事務已更新但並無提交的數據,若是該事務進行了RollBack則取出的數據就是錯誤的,因此好本身權衡利弊,通常狀況下90%以上的Select都容許髒讀,只有帳戶金額相關的不容許。

3.更新鎖

"表鎖"鎖定對該表的Select、Update、Delete操做,但不影響對該表的Insert操做也不影響以主鍵Id爲條件的Select

4.索引鎖

"行鎖+表鎖"鎖定對該表的Select、Update、Delete操做,但不影響對該表的Insert操做也不影響以主鍵Id爲條件的Select、Update、Delete,也不影響以索引列Name爲條件的Update、Delete但不能夠Select

5.主鍵鎖

"行鎖+表鎖"鎖定對該表的Select、Update、Delete操做,但不影響對該表的Insert操做也不影響以主鍵Id爲條件的Select、Update、Delete

6.悲觀鎖

7.樂觀鎖

經過版本號或者時間戳,或者使用樂觀鎖的兩種快照事務隔離級別(READ COMMITTED SNAPSHOT/SNAPSHOT)

相關文章
相關標籤/搜索