轉:sql server鎖知識及鎖應用

sql server鎖(lock)知識及鎖應用

提示:這裏所摘抄的關於鎖的知識有的是不一樣sql server版本的,對應於特定版本時會有問題。

一 關於鎖的基礎知識


(一). 爲何要引入鎖

當多個用戶同時對數據庫的併發操做時會帶來如下數據不一致的問題:html

◆丟失更新程序員

A,B兩個用戶讀同一數據並進行修改,其中一個用戶的修改結果破壞了另外一個修改的結果,好比訂票系統sql

◆髒讀數據庫

A用戶修改了數據,隨後B用戶又讀出該數據,但A用戶由於某些緣由取消了對數據的修改,數據恢復原值,此時B獲得的數據就與數據庫內的數據產生了不一致安全

◆不可重複讀服務器

A用戶讀取數據,隨後B用戶讀出該數據並修改,此時A用戶再讀取數據時發現先後兩次的值不一致網絡

併發控制的主要方法是封鎖,鎖就是在一段時間內禁止用戶作某些操做以免產生數據不一致session

 

(二) 鎖的分類

 

◆鎖的類別有兩種分法:多線程


1. 從數據庫系統的角度來看:分爲獨佔鎖(即排它鎖),共享鎖和更新鎖架構

MS-SQL Server 使用如下資源鎖模式。

鎖模式 描述

共享 (S) 用於不更改或不更新數據的操做(只讀操做),如 SELECT 語句。

更新 (U) 用於可更新的資源中。防止當多個會話在讀取、鎖定以及隨後可能進行的資源更新時發生常見形式的死鎖。

排它 (X) 用於數據修改操做,例如 INSERT、UPDATE 或 DELETE。確保不會同時同一資源進行多重更新。

意向鎖 用於創建鎖的層次結構。意向鎖的類型爲:意向共享 (IS)、意向排它 (IX) 以及與意向排它共享 (SIX)。

架構鎖 在執行依賴於表架構的操做時使用。架構鎖的類型爲:架構修改 (Sch-M) 和架構穩定性 (Sch-S)。

大容量更新 (BU) 向表中大容量複製數據並指定了 TABLOCK 提示時使用。


◆共享鎖

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


◆更新鎖

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

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


◆排它鎖

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

 

◆意向鎖

意向鎖表示 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 鎖來讀取層次結構中的底層資源。


◆獨佔鎖:

只容許進行鎖定操做的程序使用,其餘任何對他的操做均不會被接受。執行數據更新命令時,SQL Server會自動使用獨佔鎖。當對象上有其餘鎖存在時,沒法對其加獨佔鎖。

共享鎖:共享鎖鎖定的資源能夠被其餘用戶讀取,但其餘用戶沒法修改它,在執行Select時,SQL Server會對對象加共享鎖。

◆更新鎖:

當SQL Server準備更新數據時,它首先對數據對象做更新鎖鎖定,這樣數據將不能被修改,但能夠讀取。等到SQL Server肯定要進行更新數據操做時,他會自動將更新鎖換爲獨佔鎖,當對象上有其餘鎖存在時,沒法對其加更新鎖。


2. 從程序員的角度看:分爲樂觀鎖和悲觀鎖。

◆樂觀鎖:徹底依靠數據庫來管理鎖的工做。

◆悲觀鎖:程序員本身管理數據或對象上的鎖處理。


MS-SQLSERVER 使用鎖在多個同時在數據庫內執行修改的用戶間實現悲觀併發控制

 

三 鎖的粒度

 

鎖粒度是被封鎖目標的大小,封鎖粒度小則併發性高,但開銷大,封鎖粒度大則併發性低但開銷小

SQL Server支持的鎖粒度能夠分爲爲行、頁、鍵、鍵範圍、索引、表或數據庫獲取鎖

資源 描述

RID 行標識符。用於單獨鎖定表中的一行。

鍵 索引中的行鎖。用於保護可串行事務中的鍵範圍。

頁 8 千字節 (KB) 的數據頁或索引頁。

擴展盤區 相鄰的八個數據頁或索引頁構成的一組。

表 包括全部數據和索引在內的整個表。

DB 數據庫。

SQL Server 提供如下的鎖級別:

DATABASE  -- 不管什麼時候當一個SQL Server 進程正在使用除master之外的數據庫時,Lock Manager爲該進程授予數據庫級的鎖。數據庫級的鎖老是共享鎖,用於跟蹤什麼時候數據庫在使用中,以防其餘進程刪除該數據庫,將數據庫置爲脫機,或者恢復數據庫。注意,因爲master和tempdb數據庫不能被刪除或置爲脫機,因此不須要在它們之上加鎖。
FILE -- 文件級的鎖用於鎖定數據庫文件。
EXTENT -- Extent鎖用於鎖定extents,一般僅在空間分配和從新分配的時候使用。一個extent由8個連續的數據頁或索引頁組成。Extent鎖能夠是共享鎖也能夠是獨佔鎖。
ALLOCATION_UNIT -- 使用在數據庫分配單元上。
TABLE -- 這種級別的鎖將鎖定整個表,包括數據和索引。什麼時候將得到表級鎖的例子包括在Serializable隔離級別下從包含大量數據的表中選取全部的行,以及在表上執行不帶過濾條件的update或delete。
Heap or B-Tree (HOBT) -- 用於堆數據頁,或者索引的二叉樹結構。
PAGE -- 使用頁級鎖,由8KB數據或者索引信息組成的整個頁被鎖定。當須要讀取一頁的全部行或者須要執行頁級別的維護如頁拆分後更新頁指針時,將會獲取頁級鎖。
Row ID (RID) -- 使用RID鎖,頁內的單一行被鎖定。不管什麼時候當提供最大化的資源併發性訪問是有效而且可能時,將得到RID鎖。
KEY -- SQL Server使用兩種類型的Key鎖。其中一個的使用取決於當前會話的鎖隔離級別。對於運行於Read Committed 或者 Repeatable Read 隔離模式下的事務,SQL Server 鎖定與被訪問的行相關聯的的實際索引key。(若是是表的彙集索引,數據行位於索引的葉級。行上在這些你看到的是Key鎖而不是行級鎖。)若在Serializable隔離模式下,經過鎖定必定範圍的key值從而不容許新的行插入到該範圍內,SQL Server防止了「幻讀」。這些鎖於是被稱做「key-range lock」。
METADATA -- 用於鎖定系統目錄信息(元數據)。
APPLICATION -- 容許用戶定義他們本身的鎖,指定資源名稱、鎖模式、全部者、timeout間隔。

四 SQL Server 鎖類型(與粒度相對應)


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的做用是容許用戶先讀取數據(並且不阻塞其餘用戶讀數據),而且保證在後來再更新數據時,這一段時間內這些數據沒有被其餘用戶修改。

 

五 鎖定時間的長短


鎖保持的時間長度爲保護所請求級別上的資源所需的時間長度。


用於保護讀取操做的共享鎖的保持時間取決於事務隔離級別。採用 READ COMMITTED 的默認事務隔離級別時,只在讀取頁的期間內控制共享鎖。在掃描中,直到在掃描內的下一頁上獲取鎖時才釋放鎖。若是指定 HOLDLOCK 提示或者將事務隔離級別設置爲 REPEATABLE READ 或 SERIALIZABLE,則直到事務結束才釋放鎖。


根據爲遊標設置的併發選項,遊標能夠獲取共享模式的滾動鎖以保護提取。當須要滾動鎖時,直到下一次提取或關閉遊標(以先發生者爲準)時才釋放滾動鎖。可是,若是指定 HOLDLOCK,則直到事務結束才釋放滾動鎖。


用於保護更新的排它鎖將直到事務結束才釋放。

若是一個鏈接試圖獲取一個鎖,而該鎖與另外一個鏈接所控制的鎖衝突,則試圖獲取鎖的鏈接將一直阻塞到:

將衝突鎖釋放並且鏈接獲取了所請求的鎖。

鏈接的超時間隔已到期。默認狀況下沒有超時間隔,可是一些應用程序設置超時間隔以防止無限期等待 

 

六 SQL Server 中鎖的自定義

 


◆處理死鎖和設置死鎖優先級


死鎖就是多個用戶申請不一樣封鎖,因爲申請者均擁有一部分封鎖權而又等待其餘用戶擁有的部分封鎖而引發的無休止的等待


可使用SET DEADLOCK_PRIORITY控制在發生死鎖狀況時會話的反應方式。若是兩個進程都鎖定數據,而且直到其它進程釋放本身的鎖時,每一個進程才能釋放本身的鎖,即發生死鎖狀況。


◆2 處理超時和設置鎖超時持續時間。


@@LOCK_TIMEOUT 返回當前會話的當前鎖超時設置,單位爲毫秒


SET LOCK_TIMEOUT 設置容許應用程序設置語句等待阻塞資源的最長時間。當語句等待的時間大於 LOCK_TIMEOUT 設置時,系統將自動取消阻塞的語句,並給應用程序返回"已超過了鎖請求超時時段"的 1222 號錯誤信息


示例

下例將鎖超時期限設置爲 1,800 毫秒。

SET LOCK_TIMEOUT 1800


◆設置事務隔離級別。


◆對 SELECT、INSERT、UPDATE 和 DELETE 語句使用表級鎖定提示。


◆配置索引的鎖定粒度

可使用 sp_indexoption 系統存儲過程來設置用於索引的鎖定粒度

 

七 查看鎖的信息


1 執行 EXEC SP_LOCK 報告有關鎖的信息

2 查詢分析器中按Ctrl+2能夠看到鎖的信息

 

八 使用注意事項

 


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

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

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

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

4 .對全部的腳本和SP都要仔細測試,在正是版本以前。

5 全部的SP都要有錯誤處理(經過@error)

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

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

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

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

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

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

 

九 幾個有關鎖的問題

 

1 如何鎖一個表的某一行

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

SELECT * FROM table ROWLOCK WHERE id = 1

 

2 鎖定數據庫的一個表


SELECT * FROM table WITH (HOLDLOCK)

加鎖語句:

sybase: 
update 表 set col1=col1 where 1=0 ; 
MSSQL: 
select col1 from 表 (tablockx) where 1=0 ; 
oracle: 
LOCK TABLE 表 IN EXCLUSIVE MODE ; 
加鎖後其它人不可操做,直到加鎖用戶解鎖,用commit或rollback解鎖

 

◆排它鎖

新建兩個鏈接,在第一個鏈接中執行如下語句

 

  1.  
    begin tran
  2.  
    update table1 set A='aa' where B='b2'
  3.  
    waitfor delay '00:00:30' --等待30秒
  4.  
    commit tran
  5.  
    --在第二個鏈接中執行如下語句
  6.  
    begin tran
  7.  
    select * from table1 where B='b2'
  8.  
    commit tran

 

 

若同時執行上述兩個語句,則select查詢必須等待update執行完畢才能執行即要等待30秒


◆共享鎖

在第一個鏈接中執行如下語句

 

  1.  
    begin tran
  2.  
    select * from table1 holdlock --holdlock人爲加鎖
  3.  
    where B='b2'
  4.  
    waitfor delay '00:00:30' --等待30秒
  5.  
    commit tran

 

 

◆共享鎖

在第一個鏈接中執行如下語句

 

  1.  
    begin tran
  2.  
    select * from table1 holdlock --holdlock人爲加鎖
  3.  
    where B='b2'
  4.  
    waitfor delay '00:00:30' --等待30秒
  5.  
    commit tran

 

 

在第二個鏈接中執行如下語句

 

  1.  
    begin tran
  2.  
    select A,C from table1 where B='b2'
  3.  
    update table1 set A='aa' where B='b2'
  4.  
    commit tran

 

 

若同時執行上述兩個語句,則第二個鏈接中的select查詢能夠執行

而update必須等待第一個事務釋放共享鎖轉爲排它鎖後才能執行 即要等待30秒

◆死鎖

 

  1.  
    --在第一個鏈接中執行如下語句
  2.  
    begin tran
  3.  
    update table1 set A='aa' where B='b2'
  4.  
    waitfor delay '00:00:30'
  5.  
    update table2 set D='d5' where E='e1'
  6.  
    commit tran
  7.  
     
  8.  
    --在第二個鏈接中執行如下語句
  9.  
     
  10.  
    begin tran
  11.  
    update table2 set D='d5' where E='e1'
  12.  
    waitfor delay '00:00:10'
  13.  
    update table1 set A='aa' where B='b2'
  14.  
    commit tran

 

同時執行,系統會檢測出死鎖,並停止進程


十 應用程序鎖:

應用程序鎖就是客戶端代碼生成的鎖,而不是sql server自己生成的鎖

處理應用程序鎖的兩個過程

sp_getapplock 鎖定應用程序資源

sp_releaseapplock 爲應用程序資源解鎖

注意: 鎖定數據庫的一個表的區別

SELECT * FROM table WITH (HOLDLOCK) 其餘事務能夠讀取表,但不能更新刪除

SELECT * FROM table WITH (TABLOCKX) 其餘事務不能讀取表,更新和刪除

交讀事務使用行版本控制。 
使用快照隔離。
使用綁定鏈接。

 

二 鎖的分析及應用系列

 

 

1 用SqlServer Profile來查看分析鎖的信息

 

  這個工具我想你們都明白,它的監視能力真的是無所不能。。。鎖的痙攣狀態也全在它的掌握之中。

1. 首先我作一個Person表,Name字段設定4000字節,這樣一個數據頁能夠容納2條數據,以下圖:

DROP TABLE dbo.Person
CREATE TABLE Person(ID INT IDENTITY,NAME CHAR(4000) DEFAULT 'aaaaa')
--插入6條,生成3個數據頁
INSERT INTO dbo.Person DEFAULT VALUES
go 6

 

2. 下面咱們看看數據在數據頁的分佈狀況。

 

3. 而後咱們開啓Profile,在「事件選擇」的Events中選擇」Lock:Acquired「和」Lock:Released「 ,而後運行,以下圖:

 

使用測試數據

1. 首先我執行一個簡單的 SELECT * FROM dbo.Person,看看錶/數據頁/記錄的加鎖狀況。

從圖中能夠看到,select執行的大概步驟以下:

第一步:給表(Object)加上IS(意向共享鎖)。

第二步:先給1:78號數據頁加IS鎖,掃描78號數據頁,而後釋放IS鎖。

第三步:一樣的道理掃描以後的數據頁。

第四步:最後釋放表的IS鎖,結束整個鎖流程。

看完上面的一系列的Lock:Acquired 和 Lock:Released的話,你有沒有發現一個問題,不是說好給記錄(RID)加上S鎖麼???這裏沒加,是由於引擎進入78號數據頁的時候,未發現它存在IU鎖或者IX鎖。。。因此。。。這個屬於鎖的組合,後續會說。

 

2. 接下來用UPDATE dbo.Person SET NAME='bbbbb' WHERE ID=3來看看update的整個過程,乍一看,Profile捕獲到的記錄仍是比較多的,下面具體看圖:

 第一步: 給表(Object)加上IX鎖,

 第二步: 給數據頁(1:78)數據頁分配IU鎖。而後開始逐一掃描78號數據頁的RID記錄,進入前就Acquired,退出後就Released,當掃描完78號數據頁的全部RID後,再釋放78                  號數據頁的IU鎖,進入下一個數據頁。。。

 第三步: 咱們發現ID=3是在89號數據頁上,當引擎掃到該RID以後,咱們觀察到89號的數據頁由IU鎖變成了IX鎖,而且把1:89:0(slot爲0的記錄)由U鎖變成X鎖,變成X鎖                       後,就排斥了其餘全部的鎖,這時候就能夠進行Update操做了。

 第四步: 後面就繼續90號數據頁,步驟相似,第二步和第三步。

不知道細心的你有沒有發現,在Released Object以前咱們才釋放1:89:0的X鎖,而後釋放89號數據頁的IX鎖,這說明什麼???說明這個Update是貫穿於這個事務的,不像Select操做中,掃完一個數據頁就釋放一個數據頁。

 

3. 最後再看一個DELETE FROM dbo.Person WHERE ID=3 的操做。

  大概掃了一下上面的圖,或許你感受和Update操做大差不差,會掃描數據頁中的每一個記錄並加上U鎖。當在1:89:0槽位中找到了目標記錄後,而後將U鎖轉化爲X鎖,具體能夠參考Update。

2 深刻的探討鎖機制

 

 

  上一篇我只是作了一個堆表讓你們初步的認識到鎖的痙攣狀態,可是在現實世界上並無這麼簡單的事情,起碼個人表不會沒有索引對吧,,,還有就是個人表必定會有不少的鏈接過來,10:1的讀寫,不少碼農可能都會遇到相似神乎其神的死鎖,卡住,讀不出來,插不進入等等神仙的事情致使性能低下,這篇咱們一塊兒來探討下。

 

一: 當select遇到性能低下的update會怎麼樣?

1. 仍是使用原始的person表,插入6條數據,因爲是4000字節,因此兩條數據就是一個數據頁,以下圖:

1 DROP TABLE dbo.Person
2 CREATE TABLE Person(ID INT IDENTITY,NAME CHAR(4000) DEFAULT 'aaaaa')
3 --插入6條數據,恰好3個數據頁
4 INSERT INTO dbo.Person DEFAULT VALUES
5 go 6

 

2. 爲了模擬性能低下的Update操做,咱們開個顯式事務來更新ID=4的記錄,而且用profile看一下,以下圖:

1 BEGIN TRAN
2 UPDATE dbo.Person SET NAME='bbbbb' WHERE id=4



3. 而後咱們開下另外一個會話鏈接,讀取ID=6的記錄會是怎樣?好奇嗎?

1 SELECT * FROM Person WHERE ID=6

從上面流程你是否看到,當掃描到89號數據頁的slot1槽位的時候卡住了。。。我想你應該知道update正好已經給這條記錄加上了X鎖。。。若是你夠細心,你還會發現,給S鎖附加記錄的條件是在當引擎發現記錄所在的數據頁已經附加上了IX鎖的狀況下,纔給該號數據頁下的每條記錄附加S鎖,對吧。。。好了,既然在Profile上面看不到了,我仍是有其餘辦法來判斷到底select語句如今處於什麼狀態。

 

4. 使用sys.dm_tran_locks來看當前各個鏈接持有鎖的狀態。

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

仔細觀察上圖能夠看到,當前有51和52號會話,51號在1:89:1槽位上使用了X鎖而且沒有釋放,52號此時也進入了1:89:1中,而且想給該RowID附加S鎖,可是你也知道S和X鎖是排斥的,因此很無奈的一直保持等待狀態。

 

二:使用索引或許能夠幫你逃過一劫

  當你看完上面的講敘,是否是有點懼怕???要是在生產環境下出現了這種狀況,那咱們是否是死的很慘???那接下來使用索引是否是真的能夠幫咱們躲過一劫呢?下面跟我一塊兒看一看。

 

1. 新建索引index

1 -- 在ID列上建一個index
2 CREATE INDEX idx_person ON dbo.Person(ID)

 

2. 而後咱們看下數據頁的分佈狀況,能夠看到下圖中78,89,90是表數據頁,93號爲索引數據頁。

1 DBCC TRACEON(2588,3604)
2 DBCC IND(Ctrip,Person,-1)

 

3. 麻蛋的,繼續執行上面的那個慢update

BEGIN TRAN
UPDATE dbo.Person SET NAME='bbbbb' WHERE id=4

 

4. 激動人心的時刻來了,因爲數據太少,因此我這裏強制讓引擎執行我建立的索引,看看結果怎樣?

 

竟然沒卡住???如今是否是有一股強烈的好奇心來了,狗狗狗。。。立刻開啓profile,看看到底都發生了什麼?

仔細看完這個圖,是否是以爲頗有意思呢???具體步驟以下:

第一步:給表(Object)加上IS鎖。

第二步:由於要走索引,給93號索引數據頁加上IS鎖。

第三步:找到93號索引數據頁的目標key,給這個key加上S鎖,有人可能就會問了。。。這個key不就是6嘛,爲何這個key=(61005a25560e),你要是太好奇我能夠告                   訴你,年輕人說話不要太屌,每行索引記錄都有一個散列值,這個值就是根據索引的幾個字段散列出來的,好處就是防止你的索引長度過大,致使鎖這個記錄的                   時候太耗費鎖空間了。。。。若是你仍是不太相信的話,我用DBCC給你看一看。      

            

第四步:根據這個key直接跳到存放記錄的90號數據頁中,萬幸的是update的記錄恰好不在90號數據頁中。。。。就這樣躲過一劫了。。。而後select順利的讀取到了該                  讀的記錄,最後釋放相關的IS鎖。

3 nolock引起的三級事件的一些思考

 

  曾今有件事情讓我記憶猶新,那年剛來攜程不久,立刻就被安排寫一個接口,供企鵝公司調用他們員工的差旅信息,而後我就三下五除二的給寫好了,上線以後,大概過了一個月。。。DBA那邊報告數據庫出現大量鎖超時,而且及時根據sql的來源將email發到了咱們部門,指出sql讀取時間過長,而且缺乏nolock,影響了大量機票訂單入庫,而後我就拿着sql去生產環境跑了下,22s。。。花擦。。。項目上線時間過久,版本已經不存在了,沒法回滾。。。本來準備撤下接口。。。看了下撤下接口跟加上nolock時間相差很少,最後決定先加上nolock,發佈緊急單。。。而後再優化,DBA那邊暫時作手工解鎖,發上去後,最後就是損失XXXX訂單。。。定級爲三級事件。而後就是追責,固然這個責任只能有老大們去承擔了,出了此次由我引起的事件,我得思考了,出了事情對我不見得全是壞事,起碼此次會讓我銘記如心,想一想也搓,來攜程以前根本就不會關注要不要給select指定nolock,這其中也包括本身沒遇到過大數據吧,也包括本身的能力有限,只知道有鎖這個玩意,細說的話就啥也不知道了,後來才知道攜程有個規則,就是不少業務產線所寫的select都必須指定nolock,懂一點的人可能會說nolock能夠提高性能,若是你這樣說,確實是這樣,由於數據庫的鎖是有96字節開銷的,沒了鎖,也就沒有你在profile中看到accquired和released痙攣了,當你看完個人事件以後,你可能會意識到,性能提高不是最關心的,最關心就是不要出現死鎖,鎖等待。。。好了,言歸正傳,下面咱們看看到底在數據庫中能夠指定多少個鎖???

 

一:到底能夠指定多少個鎖

  這個問題有意思,咱們不須要記,只要你裝一個SQL Prompt,有了這個神器,你就知道到底有多少個?以下圖:

1 DROP TABLE dbo.Person
2 CREATE TABLE Person(ID INT IDENTITY,NAME CHAR(4000) DEFAULT 'xxxxx')
3 INSERT INTO dbo.Person DEFAULT VALUES
4 go 6

一眼掃下去,仍是蠻多的,不過你要注意了,那些所謂的XXXLock纔是咱們須要關注的,根據上面的圖,咱們大概把鎖分個類。。。

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

模式鎖:HOLDLOCK, UPDLOCK, XLOCK

接下來我從粒度鎖提及:

1. NOLOCK

  都說nolock是無鎖模式的,那究竟是怎樣的無鎖呢???到這篇爲止,你應該知道,若是不加nolock,咱們的表,數據頁是附加IS鎖的,那接下來我用profile看下二者有什麼區別。 

 

從上圖中,你會看到加上nolock以後,object上面附加了Sch-S鎖,這個鎖叫作「架構穩定鎖」,很簡單就是sql編譯時附加的一把鎖,目的就是防止在編譯時,有其餘鏈接修改表結構,而這個鎖只與Sch-M鎖衝突,與其餘鎖都兼容,這說明什麼?說明其餘鏈接鎖住了記錄也不要緊,個人nolock不跟他們打交道,這樣的話,就可能會讀到髒數據,不過不要緊,攜程的不少業務是允許髒數據的,畢竟比鎖等待,死鎖要強得多,再說nolock讀到了其餘鏈接未修改或者未提交的數據,這個機率也比較低,就算遇到了也不要緊,通常不會招來客訴的,客人或許再刷下頁面,數據或許就正確了,對不對。。。

 

2.TABLOCK

  這個仍是比較見名識義的,就是附加在table上的鎖,也就是表鎖了,很恐怖的。。。下面我舉個Update的例子,看看先後對比。

在上面你有沒有看到,X鎖已經附加到OBJECT上面去了。。。這樣的話,其餘鏈接就動不了這個Object了,只能等待。。。

 

3.  PAGLOCK

  看了名字你應該也知道,就是附加到頁面這個級別的鎖,我也舉一個Update的例子。

1 BEGIN TRAN
2 UPDATE dbo.Person SET NAME='aaaaa' WHERE ID=6
3 
4 BEGIN TRAN
5 UPDATE dbo.Person WITH(PAGLOCK) SET NAME='bbbbb' WHERE ID=4

從上面兩個圖中,你應該能夠看到,原來附加到RID上面的U鎖,因爲PagLock的提高,如今要附加到Page上面了,這個就是所謂的數據頁鎖。

 

4.TABLOCKX, ROWLOCK

   這兩個我就不細說了,TABLOCKX就是直接附加在table上的X鎖,你能夠經過select看一下。

ROWLOCK的話,默認狀況下就是ROWLOCK,好比默認的Update,你會發現RID上被附加的U鎖,這個就是行鎖。

 

5.UPDLOCK

 這個鎖仍是蠻有意思的,它就是update鎖,若是你select下,它會呈現update的鎖痙攣效果。

  

6. XLOCK

  知道了UPDLOCK鎖,我想XLOCK你也應該明白了。。。它就是delete鎖,即排他鎖,我可讓select帶上排他鎖。

 

7.HOLDLOCK

  最後一個我也沒鬧明白,聽說是讓語句在整個事務中持有鎖,而後我就用select和update調試一下。

1 SELECT * FROM dbo.Person(HOLDLOCK)
2 UPDATE dbo.Person WITH(HOLDLOCK) SET NAME='bbbbb' WHERE ID=4

 

三 SQL Server 鎖機制 悲觀鎖 樂觀鎖 實測解析


在使用SQL時,大都會遇到這樣的問題,你Update一條記錄時,須要經過Select來檢索出其值或條件,而後在經過這個值來執行修改操做。

但當以上操做放到多線程中併發處理時會出現問題:某線程select了一條記錄但還沒來得及update時,另外一個線程仍然可能會進來select到同一條記錄。

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


1. 把select放在事務中, 不然select完成, 鎖就釋放了
2. 要阻止另外一個select , 則要手工加鎖, select 默認是共享鎖, select之間的共享鎖是不衝突的, 因此, 若是隻是共享鎖, 即便鎖沒有釋放, 另外一個select同樣能夠下共享鎖, 從而select出數據 

  1.  
    BEGIN TRAN
  2.  
    SELECT * FROM Table WITH(UPDLOCK)
  3.  
    --或者 SELECT * FROM Table WITH(TABLOCKX, READPAST) 具體狀況而定。
  4.  
    UPDATE ....
  5.  
    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)

實際開始動手用代碼說話吧!

SQLServer2012在查詢分析器裏面開兩個鏈接

插入鎖:


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

  1.  
    ------------------A鏈接 Insert Lock-------------------
  2.  
    BEGIN TRAN
  3.  
    INSERT INTO dbo.UserInfo
  4.  
    ( Name, Age, Mobile, AddTime, Type )
  5.  
    VALUES ( 'eee', -- Name - varchar(50)
  6.  
    2, -- Age - int
  7.  
    '555', -- Mobile - char(11)
  8.  
    GETDATE(), -- AddTime - datetime
  9.  
    0 -- Type - int
  10.  
    )
  11.  
     
  12.  
    SELECT resource_type, request_mode,COUNT(*) FROM sys.dm_tran_locks
  13.  
    WHERE request_session_id=@@SPID
  14.  
    GROUP BY resource_type,request_mode
  15.  
    --ROLLBACK TRAN
  16.  
     
  17.  
    ------------------------B鏈接 Insert Lock------------------------
  18.  
    INSERT INTO dbo.UserInfo
  19.  
    ( Name, Age, Mobile, AddTime, Type )
  20.  
    VALUES ( 'fff', -- Name - varchar(50)
  21.  
    2, -- Age - int
  22.  
    '123', -- Mobile - char(11)
  23.  
    GETDATE(), -- AddTime - datetime
  24.  
    1 -- Type - int
  25.  
    ) --能夠執行插入
  26.  
     
  27.  
    SELECT * FROM dbo.UserInfo --須要等待解鎖
  28.  
    SELECT * FROM dbo.UserInfo WHERE Age=1 --須要等待解鎖
  29.  
    SELECT * FROM dbo.UserInfo WHERE Id=3 --能夠執行查詢(根據主鍵能夠)
  30.  
    SELECT * FROM dbo.UserInfo WITH(NOLOCK) --能夠執行查詢(在一個事務中,有更新字段但尚未提交,此時就會查處髒數據)
  31.  
    SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Age=1 --能夠執行查詢
  32.  
    UPDATE dbo.UserInfo SET Type=5 WHERE Name='fff' --須要等待解鎖
  33.  
    DELETE FROM dbo.UserInfo WHERE Name='fff' --須要等待解鎖

 

更新鎖:


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

  1.  
    -----------------------A鏈接 Update Lock-----------------------
  2.  
    BEGIN TRAN
  3.  
    UPDATE dbo.UserInfo SET Name = 'eee' WHERE Age = 2
  4.  
     
  5.  
    SELECT resource_type, request_mode,COUNT(*) FROM sys.dm_tran_locks
  6.  
    WHERE request_session_id=@@SPID
  7.  
    GROUP BY resource_type,request_mode
  8.  
     
  9.  
    --ROLLBACK TRAN
  10.  
     
  11.  
    ------------------------B鏈接 Update Lock------------------------
  12.  
    INSERT INTO dbo.UserInfo
  13.  
    ( Name, Age, Mobile, AddTime, Type )
  14.  
    VALUES ( 'ppp', -- Name - varchar(50)
  15.  
    15, -- Age - int
  16.  
    '666', -- Mobile - char(11)
  17.  
    GETDATE(), -- AddTime - datetime
  18.  
    9 -- Type - int
  19.  
    ) --能夠執行插入
  20.  
    SELECT * FROM dbo.UserInfo --須要等待解鎖
  21.  
    SELECT * FROM dbo.UserInfo WHERE Name='ppp' --須要等待解鎖
  22.  
    SELECT * FROM dbo.UserInfo WHERE Id=3 --能夠執行查詢(根據主鍵能夠)
  23.  
    SELECT * FROM dbo.UserInfo WITH(NOLOCK) --能夠執行查詢(在一個事務中,有更新字段但尚未提交,此時就會查處髒數據)
  24.  
    SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Name = 'ppp' --能夠執行查詢
  25.  
    UPDATE dbo.UserInfo SET Age=8 WHERE Name='ccc' --須要等待解鎖
  26.  
    DELETE dbo.UserInfo WHERE Age = 5 --須要等待解鎖

 

主鍵鎖:


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

  1.  
    ------------------------A鏈接 Key Lock--------------------
  2.  
    BEGIN TRAN
  3.  
    UPDATE dbo.UserInfo SET Name='hhh' WHERE Id=3 --以主鍵爲條件
  4.  
     
  5.  
    SELECT resource_type, request_mode,COUNT(*) FROM sys.dm_tran_locks
  6.  
    WHERE request_session_id=@@SPID
  7.  
    GROUP BY resource_type,request_mode
  8.  
     
  9.  
    --ROLLBACK TRAN
  10.  
     
  11.  
    ------------------------B鏈接 Key Lock----------------------
  12.  
    INSERT INTO dbo.UserInfo
  13.  
    ( Name, Age, Mobile, AddTime, Type )
  14.  
    VALUES ( 'kkk', -- Name - varchar(50)
  15.  
    18, -- Age - int
  16.  
    '234', -- Mobile - char(11)
  17.  
    GETDATE(), -- AddTime - datetime
  18.  
    7 -- Type - int
  19.  
    ) --能夠執行插入
  20.  
    SELECT * FROM dbo.UserInfo WITH(NOLOCK) --能夠執行查詢(在一個事務中,有更新字段但尚未提交,此時就會查處髒數據)
  21.  
    SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Name = 'kkk' --能夠執行查詢
  22.  
     
  23.  
    -----//全表查詢及操做正在處理的行
  24.  
    SELECT * FROM dbo.UserInfo --須要等待解鎖
  25.  
    SELECT * FROM dbo.UserInfo WHERE Id=3 --須要等待解鎖(根據主鍵,但與A鏈接操做相同行不可)
  26.  
    UPDATE dbo.UserInfo SET Name='mmm' WHERE Id=3 --須要等待解鎖(根據主鍵,但與A鏈接操做相同行不可)
  27.  
    DELETE dbo.UserInfo WHERE Id=3 --須要等待解鎖(根據主鍵,但與A鏈接操做相同行不可)
  28.  
    -----//使用非主鍵爲條件的操做
  29.  
    SELECT * FROM dbo.UserInfo WHERE Name='aaa' --須要等待解鎖(非主鍵不可)
  30.  
    UPDATE dbo.UserInfo SET Name='ooo' WHERE Name='aaa' --須要等待解鎖(非主鍵不可)
  31.  
    DELETE dbo.UserInfo WHERE Name='aaa' --須要等待解鎖(非主鍵不可)
  32.  
    -----//使用主鍵爲條件的操做
  33.  
    SELECT * FROM dbo.UserInfo WHERE id=1 --能夠執行查詢(根據主鍵能夠)
  34.  
    UPDATE dbo.UserInfo SET Name='yyy' WHERE Id=1 --能夠執行更新(根據主鍵能夠)
  35.  
    DELETE dbo.UserInfo WHERE Id=1 --能夠執行刪除(根據主鍵能夠)

 

索引鎖:


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

  1.  
    ------------------------A鏈接 Index Lock--------------------
  2.  
    DROP INDEX dbo.UserInfo.Index_UserInfo_Name
  3.  
    CREATE INDEX Index_UserInfo_Name ON dbo.UserInfo(Name)
  4.  
     
  5.  
    BEGIN TRAN
  6.  
    UPDATE dbo.UserInfo SET age=66 WHERE Name='ddd' --使用name索引列爲條件
  7.  
     
  8.  
    SELECT resource_type, request_mode,COUNT(*) FROM sys.dm_tran_locks
  9.  
    WHERE request_session_id=@@SPID
  10.  
    GROUP BY resource_type,request_mode
  11.  
     
  12.  
    --ROLLBACK TRAN
  13.  
     
  14.  
    ----------------------B鏈接 Index Lock-------------------
  15.  
    INSERT INTO dbo.UserInfo
  16.  
    ( Name, Age, Mobile, AddTime, Type )
  17.  
    VALUES ( 'iii', -- Name - varchar(50)
  18.  
    20, -- Age - int
  19.  
    '235235235', -- Mobile - char(11)
  20.  
    GETDATE(), -- AddTime - datetime
  21.  
    12 -- Type - int
  22.  
    ) --能夠執行插入
  23.  
    SELECT * FROM dbo.UserInfo WITH(NOLOCK) --能夠執行查詢(在一個事物中,有更新字段但尚未提交,此時就會查處髒數據)
  24.  
    SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Name = 'kkk' --能夠執行查詢
  25.  
     
  26.  
    -----//全表查詢及操做正在處理的行
  27.  
    SELECT * FROM dbo.UserInfo --須要等待解鎖
  28.  
    SELECT * FROM dbo.UserInfo WHERE Id=4 --須要等待解鎖(根據主鍵,但與A鏈接操做相同行不可)
  29.  
    UPDATE dbo.UserInfo SET Name='mmm' WHERE Id=4 --須要等待解鎖(根據主鍵,但與A鏈接操做相同行不可)
  30.  
    DELETE dbo.UserInfo WHERE Id=4 --須要等待解鎖(根據主鍵,但與A鏈接操做相同行不可)
  31.  
    -----//使用非主鍵非索引爲條件的操做
  32.  
    SELECT * FROM dbo.UserInfo WHERE Age=5 --須要等待解鎖(非主鍵不可)
  33.  
    UPDATE dbo.UserInfo SET Name='ooo' WHERE Age=5 --須要等待解鎖(非主鍵不可)
  34.  
    DELETE dbo.UserInfo WHERE Age=5 --須要等待解鎖(非主鍵不可)
  35.  
    -----//使用主鍵爲條件的操做
  36.  
    SELECT * FROM dbo.UserInfo WHERE Id=1 --能夠執行更新(根據主鍵能夠)
  37.  
    UPDATE dbo.UserInfo SET Name='yyy' WHERE Id=1 --能夠執行更新(根據主鍵能夠)
  38.  
    DELETE dbo.UserInfo WHERE Id=1 --能夠執行刪除(根據主鍵能夠)
  39.  
    -----//使用索引爲條件的操做
  40.  
    SELECT * FROM dbo.UserInfo WHERE Name='aaa' --須要等待解鎖(非主鍵不可)
  41.  
    UPDATE dbo.UserInfo SET Name='ooo' WHERE Name='aaa' --能夠執行更新(根據索引能夠)
  42.  
    DELETE dbo.UserInfo WHERE Name='aaa' --能夠執行刪除(根據索引能夠)

 

悲觀鎖(更新鎖-人工手動設置上鎖):


結論:能夠理解爲在使用版本控制軟件的時候A遷出了一個文件,而且8i將這個87文件鎖定,B就沒法再遷出該文件了,直到A遷入解鎖後才能被其餘人遷出。

  1.  
    ------------------------A鏈接 Update Lock(悲觀鎖)---------------------
  2.  
    BEGIN TRAN
  3.  
    SELECT * FROM dbo.UserInfo WITH(UPDLOCK) WHERE Id=2
  4.  
     
  5.  
    SELECT resource_type, request_mode,COUNT(*) FROM sys.dm_tran_locks
  6.  
    WHERE request_session_id=@@SPID
  7.  
    GROUP BY resource_type,request_mode
  8.  
     
  9.  
    --COMMIT TRAN
  10.  
    --ROLLBACK TRAN
  11.  
     
  12.  
    ---------------------------B鏈接 Update Lock(悲觀鎖)-------------------------
  13.  
    SELECT * FROM dbo.UserInfo --能夠執行查詢
  14.  
    SELECT * FROM dbo.UserInfo WHERE id=2 --能夠執行查詢
  15.  
    SELECT * FROM dbo.UserInfo WHERE Name='ooo' --能夠執行查詢
  16.  
     
  17.  
    UPDATE dbo.UserInfo SET Age=3 WHERE id=1 --能夠執行更新(根據主鍵能夠)
  18.  
    UPDATE dbo.UserInfo SET Age=3 WHERE Name='ccc' --須要等待解鎖(非主鍵不可)
  19.  
     
  20.  
    DELETE dbo.UserInfo WHERE id=1 --能夠執行更新(根據主鍵能夠)
  21.  
    DELETE dbo.UserInfo WHERE name='ccc' --須要等待解鎖(非主鍵不可)

 

樂觀鎖(人工經過邏輯在數據庫中模擬鎖)


結論:能夠理解爲一樣在使用版本控制軟件的時候A遷出了一個文件,B也能夠遷出該文件,兩我的均可以對此文件進行修改,其中一我的先進行提交的時候,版本並無變化因此能夠正常提交,另外一個後提交的時候,發現版本增長不對稱了,就提示衝突由用戶來選擇如何進行合併再從新進行提交。

  1.  
    --------------------------A客戶端鏈接 Lock(樂觀鎖)------------------------
  2.  
    --DROP TABLE Coupon
  3.  
    -----------------建立優惠券表-----------------
  4.  
    CREATE TABLE Coupon
  5.  
    (
  6.  
    Id INT PRIMARY KEY IDENTITY(1,1),
  7.  
    Number VARCHAR(50) NOT NULL,
  8.  
    [ User] VARCHAR(50),
  9.  
    UseTime DATETIME,
  10.  
    IsFlag BIT DEFAULT(0) NOT NULL,
  11.  
    CreateTime DATETIME DEFAULT(GETDATE()) NOT NULL
  12.  
    )
  13.  
    INSERT INTO dbo.Coupon(Number) VALUES ( '10000001')
  14.  
    INSERT INTO dbo.Coupon(Number) VALUES ( '10000002')
  15.  
    INSERT INTO dbo.Coupon(Number) VALUES ( '10000003')
  16.  
    INSERT INTO dbo.Coupon(Number) VALUES ( '10000004')
  17.  
    INSERT INTO dbo.Coupon(Number) VALUES ( '10000005')
  18.  
    INSERT INTO dbo.Coupon(Number) VALUES ( '10000006')
  19.  
     
  20.  
    --SELECT * FROM dbo.Coupon WITH(NOLOCK) --查詢數據
  21.  
    --UPDATE Coupon SET [User]=NULL, UseTime=NULL, IsFlag=0 --還原數據
  22.  
     
  23.  
    -----------------一、模擬高併發普通更新-----------------
  24.  
    DECLARE @User VARCHAR(50) --模擬要使用優惠券的用戶
  25.  
    DECLARE @TempId INT --模擬抽選出來的要使用的優惠券
  26.  
    SET @User='a'
  27.  
    BEGIN TRAN
  28.  
    SELECT @TempId=Id FROM dbo.Coupon WHERE IsFlag=0 --高併發時此語句有可能另一個該事務已取出的Id
  29.  
    --WAITFOR DELAY '00:00:05' --改用此方式要開兩個SQL Management客戶端
  30.  
    UPDATE dbo.Coupon SET IsFlag=1, [User]=@User, UseTime=GETDATE() WHERE Id=@TempId
  31.  
    COMMIT TRAN
  32.  
    --ROLLBACK TRAN
  33.  
     
  34.  
    -----------------二、悲觀鎖解決方案-----------------
  35.  
    DECLARE @User VARCHAR(50) --模擬要使用優惠券的用戶
  36.  
    DECLARE @TempId INT --模擬抽選出來的要使用的優惠券
  37.  
    SET @User='a'
  38.  
    BEGIN TRAN
  39.  
    SELECT @TempId=Id FROM dbo.Coupon WITH(UPDLOCK) WHERE IsFlag=0 --高併發時此語句會鎖定取出的Id數據行
  40.  
    --WAITFOR DELAY '00:00:05' --改用此方式要開兩個SQL Management客戶端
  41.  
    UPDATE dbo.Coupon SET IsFlag=1, [User]=@User, UseTime=GETDATE() WHERE Id=@TempId
  42.  
    COMMIT TRAN
  43.  
    --ROLLBACK TRAN
  44.  
     
  45.  
    -----------------三、樂觀鎖解決方案-----------------
  46.  
    ALTER TABLE dbo.Coupon ADD RowVer ROWVERSION NOT NULL --增長數據行版本戳類型字段(微軟新推薦數據字段,該字段每張表只能有一個,會在建立行或更新行時自動進行修改無需人爲干涉,該字段不能創建索引及主鍵由於會頻繁修改)
  47.  
     
  48.  
     
  49.  
    DECLARE @User VARCHAR(50) --模擬要使用優惠券的用戶
  50.  
    DECLARE @TempId INT --模擬抽選出來的要使用的優惠券
  51.  
    DECLARE @RowVer BINARY(8) --抽選出來的優惠券的版本(ROWVERSION數據類型存儲大小爲8字節)
  52.  
    SET @User='a'
  53.  
     
  54.  
    BEGIN TRY
  55.  
    BEGIN TRAN
  56.  
    SELECT @TempId=Id, @RowVer=RowVer FROM dbo.Coupon WHERE IsFlag=0 --取出可用的Id及對應的版本戳
  57.  
    --WAITFOR DELAY '00:00:05' --改用此方式要開兩個SQL Management客戶端
  58.  
    UPDATE dbo.Coupon SET IsFlag=1, [User]=@User, UseTime=GETDATE() WHERE Id=@TempId AND RowVer=@RowVer
  59.  
    IF(@@ROWCOUNT > 0)
  60.  
    BEGIN
  61.  
    PRINT( '修改爲功')
  62.  
    COMMIT TRAN
  63.  
    END
  64.  
    ELSE
  65.  
    BEGIN
  66.  
    PRINT( '該數據已被其餘用戶修改')
  67.  
    ROLLBACK TRAN
  68.  
    END
  69.  
    END TRY
  70.  
    BEGIN CATCH
  71.  
    ROLLBACK TRAN
  72.  
    END CATCH
  73.  
     
  74.  
    --------------------------B客戶端鏈接 Lock(樂觀鎖)------------------------
  75.  
    --此測試須要開兩個SQL Management Studio客戶端,在A客戶端使用WAITFOR DELAY來模擬併發佔用,在B客戶端執行與A客戶端相同的SQL腳本便可(註釋掉WAITFOR),因此在此不放相同代碼了。



在樂觀鎖和悲觀鎖之間進行選擇的標準是:衝突的頻率與嚴重性。若是衝突不多,或者衝突的後果不會很嚴重,那麼一般狀況下應該選擇樂觀鎖,由於它能獲得更好的併發性,並且更容易實現。可是,若是衝突的結果對於用戶來講痛苦的,那麼就須要使用悲觀策略。

我認爲若是同一張表的併發很高,但併發處理同一條數據的衝突概率很低,那就應該使用樂觀鎖,反之,若是同一張表的併發不高,但同時處理同一條數據的概率很高,就應該使用悲觀鎖。

四 SQL Server 中WITH (NOLOCK)淺析

 

概念介紹

開發人員喜歡在SQL腳本中使用WITH(NOLOCK), WITH(NOLOCK)實際上是表提示(table_hint)中的一種。它等同於 READUNCOMMITTED 。 具體的功能做用以下所示(摘自MSDN):

   1: 指定容許髒讀。不發佈共享鎖來阻止其餘事務修改當前事務讀取的數據,其餘事務設置的排他鎖不會阻礙當前事務讀取鎖定數據。容許髒讀可能產生較多的併發操做,但其代價是讀取之後會被其餘事務回滾的數據修改。這可能會使您的事務出錯,向用戶顯示從未提交過的數據,或者致使用戶兩次看到記錄(或根本看不到記錄)。有關髒讀、不可重複讀和幻讀的詳細信息,請參閱併發影響

   2: READUNCOMMITTED 和 NOLOCK 提示僅適用於數據鎖。全部查詢(包括那些帶有 READUNCOMMITTED 和 NOLOCK 提示的查詢)都會在編譯和執行過程當中獲取 Sch-S(架構穩定性)鎖。所以,當併發事務持有表的 Sch-M(架構修改)鎖時,將阻塞查詢。例如,數據定義語言 (DDL) 操做在修改表的架構信息以前獲取 Sch-M 鎖。全部併發查詢(包括那些使用 READUNCOMMITTED 或 NOLOCK 提示運行的查詢)都會在嘗試獲取 Sch-S 鎖時被阻塞。相反,持有 Sch-S 鎖的查詢將阻塞嘗試獲取 Sch-M 鎖的併發事務。有關鎖行爲的詳細信息,請參閱鎖兼容性(數據庫引擎)

   3:  不能爲經過插入、更新或刪除操做修改過的表指定 READUNCOMMITTED 和 NOLOCK。SQL Server 查詢優化器忽略 FROM 子句中應用於 UPDATE 或 DELETE 語句的目標表的 READUNCOMMITTED 和 NOLOCK 提示。

功能與缺陷

    使用WIHT(NOLOCK)有利也有弊,因此在決定使用以前,你必定須要瞭解清楚WITH(NOLOCK)的功能和缺陷,看其是否適合你的業務需求,不要以爲它能提高性能,稀裏糊塗的就使用它。

    1:使用WITH(NOLOCK)時查詢不受其它排他鎖阻塞

    打開會話窗口1,執行下面腳本,不提交也不回滾事務,模擬事務真在執行過程中

BEGIN TRAN
 
       UPDATE TEST SET NAME='Timmy' WHERE OBJECT_ID =1;
 
       --ROLLBACK
 

   

   打開會話窗口2,執行下面腳本,你會發現執行結果一直查詢不出來(其實才兩條記錄)。當前會話被阻塞了

SELECT * FROM TEST;

    打開會話窗口3,執行下面腳本,查看阻塞狀況,你會發如今會話2被會話1給阻塞了,會話2的等待類型爲LCK_M_S:「當某任務正在等待獲取共享鎖時出現」

 
 
  SELECT wt.blocking_session_id                    AS BlockingSessesionId
        ,sp.program_name                           AS ProgramName
        ,COALESCE(sp.LOGINAME, sp.nt_username)     AS HostName    
        ,ec1.client_net_address                    AS ClientIpAddress
        ,db.name                                   AS DatabaseName        
        ,wt.wait_type                              AS WaitType                    
        ,ec1.connect_time                          AS BlockingStartTime
        ,wt.WAIT_DURATION_MS/1000                  AS WaitDuration
        ,ec1.session_id                            AS BlockedSessionId
        ,h1.TEXT                                   AS BlockedSQLText
        ,h2.TEXT                                   AS BlockingSQLText
  FROM sys.dm_tran_locks AS tl
  INNER JOIN sys.databases db
    ON db.database_id = tl.resource_database_id
  INNER JOIN sys.dm_os_waiting_tasks AS wt
    ON tl.lock_owner_address = wt.resource_address
  INNER JOIN sys.dm_exec_connections ec1
    ON ec1.session_id = tl.request_session_id
  INNER JOIN sys.dm_exec_connections ec2
    ON ec2.session_id = wt.blocking_session_id
  LEFT OUTER JOIN master.dbo.sysprocesses sp
    ON SP.spid = wt.blocking_session_id
  CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1
  CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2

 

 

此時查看會話1(會話1的會話ID爲53,執行腳本1前,能夠用SELECT  @@spid查看會話ID)的鎖信息狀況,你會發現表TEST(ObjId=1893581784)持有的鎖信息以下所示

打開會話窗口4,執行下面腳本.你會發現查詢結果很快就出來,會話4並不會被會話1阻塞。

    SELECT * FROM TEST WITH(NOLOCK)

從上面模擬的這個小例子能夠看出,正是因爲加上WITH(NOLOCK)提示後,會話1中事務設置的排他鎖不會阻礙當前事務讀取鎖定數據,因此會話4不會被阻塞,從而提高併發時查詢性能。

2:WITH(NOLOCK) 不發佈共享鎖來阻止其餘事務修改當前事務讀取的數據,這個就不舉例子了。

本質上WITH(NOLOCK)是經過減小鎖和不受排它鎖影響來減小阻塞,從而提升併發時的性能。所謂凡事有利也有弊,WITH(NOLOCK)在提高性能的同時,也會產生髒讀現象。

以下所示,表TEST有兩條記錄,我準備更新OBJECT_ID=1的記錄,此時事務既沒有提交也沒有回滾

BEGIN TRAN 
 
UPDATE TEST SET NAME='Timmy' WHERE OBJECT_ID =1; 
 
--ROLLBACK 
 

此時另一個會話使用WITH(NOLOCK)查到的記錄爲未提交的記錄值

假如因爲某種緣由,該事務回滾了,那麼咱們讀取到的OBJECT_ID=1的記錄就是一條髒數據。

髒讀又稱無效數據的讀出,是指在數據庫訪問中,事務T1將某一值修改,而後事務T2讀取該值,此後T1由於某種緣由撤銷對該值的修改,這就致使了T2所讀取到的數據是無效的。

WITH(NOLOCK)使用場景

何時可使用WITH(NOLOCK)? 何時不能使用WITH(NOLOCK),這個要視你係統業務狀況,綜合考慮性能狀況與業務要求來決定是否使用WITH(NOLOCK), 例如涉及到金融或會計成本之類的系統,出現髒讀那是要產生嚴重問題的。關鍵業務系統也要慎重考慮。大致來講通常有下面一些場景可使用WITH(NOLOCK)

   1: 基礎數據表,這些表的數據不多變動。

   2:歷史數據表,這些表的數據不多變動。

   3:業務容許髒讀狀況出現涉及的表。

   4:數據量超大的表,出於性能考慮,而容許髒讀。

另一點就是不要濫用WITH(NOLOCK),我發現有個奇怪現象,不少開發知道WITH(NOLOCK),可是有不瞭解髒讀,習慣性的使用WITH(NOLOCK)。

WITH(NOLOCK)與 NOLOCK區別

爲了搞清楚WITH(NOLOCK)與NOLOCK的區別,我查了大量的資料,咱們先看看下面三個SQL語句有啥區別

    SELECT * FROM TEST NOLOCK

    SELECT * FROM TEST (NOLOCK);

    SELECT * FROM TEST WITH(NOLOCK);

上面的問題歸納起來也就是說NOLOCK、(NOLOCK)、 WITH(NOLOCK)的區別:

1: NOLOCK這樣的寫法,其實NOLOCK其實只是別名的做用,而沒有任何實質做用。因此不要粗心將(NOLOCK)寫成NOLOCK

2:(NOLOCK)與WITH(NOLOCK)其實功能上是同樣的。(NOLOCK)只是WITH(NOLOCK)的別名,可是在SQL Server 2008及之後版本中,(NOLOCK)不推薦使用了,"不借助 WITH 關鍵字指定表提示」的寫法已通過時了。 具體參見MSDN http://msdn.microsoft.com/zh-cn/library/ms143729%28SQL.100%29.aspx

    2.1  至於網上說WITH(NOLOCK)在SQL SERVER 2000不生效,我驗證後發現徹底是個謬論。

    2.2  在使用連接服務器的SQL當中,(NOLOCK)不會生效,WITH(NOLOCK)纔會生效。以下所示

    消息 4122,級別 16,狀態 1,第 1 行

    Remote table-valued function calls are not allowed.

3.語法上有些許出入,以下所示

這種語法會報錯
SELECT  * FROM   sys.indexes  WITH(NOLOCK) AS i
-Msg 156, Level 15, State 1, Line 1
-Incorrect syntax near the keyword 'AS'.
 
這種語法正常
SELECT  * FROM   sys.indexes  (NOLOCK) AS i
 
能夠所有改寫爲下面語法
 
SELECT  * FROM   sys.indexes   i WITH(NOLOCK) 
 
 
SELECT  * FROM   sys.indexes   i (NOLOCK) 

WITH(NOLOCK)會不會產生鎖

    不少人誤覺得使用了WITH(NOLOCK)後,數據庫庫不會產生任何鎖。實質上,使用了WITH(NOLOCK)後,數據庫依然對該表對象生成Sch-S(架構穩定性)鎖以及DB類型的共享鎖, 以下所示,能夠在一個會話中查詢一個大表,而後在另一個會話中查看鎖信息(也可使用SQL Profile查看會話鎖信息)

    不使用WTIH(NOLOCK)

  使用WITH(NOLOCK)

  從上能夠看出使用WITH(NOLOCK)後,數據庫並非不生成相關鎖。  對比能夠發現使用WITH(NOLOCK)後,數據庫只會生成DB類型的共享鎖、以及TAB類型的架構穩定性鎖.

另外,使用WITH(NOLOCK)並非說就不會被其它會話阻塞,依然可能會產生Schema Change Blocking

會話1:執行下面SQL語句,暫時不提交,模擬事務正在執行

BEGIN TRAN 
 
  ALTER TABLE TEST ADD Grade VARCHAR(10) ; 
 

會話2:執行下面語句,你會發現會話被阻塞,截圖以下所示。

SELECT * FROM TEST WITH(NOLOCK)

 

 

-----

本文引用瞭如下網址內容:

http://www.cnblogs.com/huangxincheng/p/4292320.html

http://blog.itpub.net/13651903/viewspace-1091664/

http://www.cnblogs.com/kerrycode/p/3946268.html

http://www.cnblogs.com/taiyonghai/p/5674462.html

--- end ---

相關文章
相關標籤/搜索