sql 鎖類型與鎖機制


SQL Server鎖類型(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的作用是允許用戶先讀取數據(而且不阻塞其他用戶讀數據),並且保證在后來再更新數據時,這一段時間內這些數據沒有被其他用戶修改。
-----------------

一. 為什么要引入鎖


當多個用戶同時對數據庫的並發操作時會帶來以下數據不一致的問題:


◆丟失更新

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


◆臟讀

A用戶修改了數據,隨后B用戶又讀出該數據,但A用戶因為某些原因取消了對數據的修改,數據恢復原值,此時B得到的數據就與數據庫內的數據產生了不一致


◆不可重復讀

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


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


二 鎖的分類


◆鎖的類別有兩種分法:


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 數據庫。


四 鎖定時間的長短


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


用於保護讀取操作的共享鎖的保持時間取決於事務隔離級別。采用 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事務的默認級別。不推薦強行加鎖


解決問題 如何對行 表 數據庫加鎖


八 幾個有關鎖的問題


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解鎖

 


幾個例子幫助大家加深印象


設table1(A,B,C) 
A B C 
a1 b1 c1 
a2 b2 c2 
a3 b3 c3

 

◆排它鎖

新建兩個連接

在第一個連接中執行以下語句


begin tran 
update table1 
set A='aa' 
where B='b2' 
waitfor delay '00:00:30' --等待30秒 
commit tran 
在第二個連接中執行以下語句 
begin tran 
select * from table1 
where B='b2' 
commit tran

 

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


◆共享鎖

在第一個連接中執行以下語句


begin tran 
select * from table1 holdlock -holdlock人為加鎖 
where B='b2' 
waitfor delay '00:00:30' --等待30秒 
commit tran

 

◆共享鎖

在第一個連接中執行以下語句


begin tran 
select * from table1 holdlock -holdlock人為加鎖 
where B='b2' 
waitfor delay '00:00:30' --等待30秒 
commit tran

 

在第二個連接中執行以下語句


begin tran 
select A,C from table1 
where B='b2' 
update table1 
set A='aa' 
where B='b2' 
commit tran

 


若同時執行上述兩個語句,則第二個連接中的select查詢可以執行

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

◆死鎖


增設table2(D,E) 
D E 
d1 e1 
d2 e2 
在第一個連接中執行以下語句 
begin tran 
update table1 
set A='aa' 
where B='b2' 
waitfor delay '00:00:30' 
update table2 
set D='d5' 
where E='e1' 
commit tran

 

在第二個連接中執行以下語句


begin tran 
update table2 
set D='d5' 
where E='e1' 
waitfor delay '00:00:10' 
update table1 
set A='aa' 
where B='b2' 
commit tran

 

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


補充一點:

SQL Server2000支持的表級鎖定提示


HOLDLOCK 持有共享鎖,直到整個事務完成,應該在被鎖對象不需要時立即釋放,等於SERIALIZABLE事務隔離級別


NOLOCK 語句執行時不發出共享鎖,允許臟讀 ,等於 READ UNCOMMITTED事務隔離級別


PAGLOCK 在使用一個表鎖的地方用多個頁鎖


READPAST 讓SQL Server跳過任何鎖定行,執行事務,適用於READ UNCOMMITTED事務隔離級別只跳過RID鎖,不跳過頁,區域和表鎖


ROWLOCK 強制使用行鎖


TABLOCKX 強制使用獨占表級鎖,這個鎖在事務期間阻止任何其他事務使用這個表


UPLOCK 強制在讀表時使用更新而不用共享鎖


應用程序鎖:

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


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


sp_getapplock 鎖定應用程序資源


sp_releaseapplock 為應用程序資源解鎖


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


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


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

交讀事務使用行版本控制。 
使用快照隔離。
使用綁定連接。
-----------------------------
分析及解決SQLServer死鎖問題(續) 
上文中,我們解決了那個場景的死鎖問題。這次,我們分析一下,為什么會死鎖呢?再回顧一下兩個sp的寫法:
   CREATE PROC p1 @p1 int AS 
      SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
   GO
   CREATE PROC p2 @p1 int AS
         UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
         UPDATE t1 SET c2 = c2-1 WHERE c1 = @p1
   GO

   很奇怪吧!p1沒有insert,沒有delete,沒有update,只是一個select,p2才是update。這個和我們前面說過的,trans1里面updata A,update B;trans2里面upate B,update A,根本不貼邊啊!
   那么,什么導致了死鎖?

   需要從事件日志中,看sql的死鎖信息:
   Spid X is running this query (line 2 of proc [p1], inputbuffer “… EXEC p1 4 …”): 
   SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
   Spid Y is running this query (line 2 of proc [p2], inputbuffer “EXEC p2 4”): 
   UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
                
   The SELECT is waiting for a Shared KEY lock on index t1.cidx.  The UPDATE holds a conflicting X lock. 
   The UPDATE is waiting for an eXclusive KEY lock on index t1.idx1.  The SELECT holds a conflicting S lock.

   首先,我們看看p1的執行計划。怎么看呢?可以執行set statistics profile on,這句就可以了。下面是p1的執行計划
   SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
        |--Nested Loops(Inner Join, OUTER REFERENCES:([Uniq1002], [t1].[c1]))
               |--Index Seek(OBJECT:([t1].[idx1]), SEEK:([t1].[c2] >= [@p1] AND [t1].[c2] <= [@p1]+(1)) ORDERED FORWARD)
                     |--Clustered Index Seek(OBJECT:([t1].[cidx]), SEEK:([t1].[c1]=[t1].[c1] AND [Uniq1002]=[Uniq1002]) LOOKUP ORDERED FORWARD)

   我們看到了一個nested loops,第一行,利用索引t1.c2來進行seek,seek出來的那個rowid,在第二行中,用來通過聚集索引來查找整行的數據。這是什么?就是bookmark lookup啊!為什么?因為我們需要的c2、c3不能完全的被索引t1.c1帶出來,所以需要書簽查找。
   好,我們接着看p2的執行計划。
   UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
         |--Clustered Index Update(OBJECT:([t1].[cidx]), OBJECT:([t1].[idx1]), SET:([t1].[c2] = [Expr1004]))
               |--Compute Scalar(DEFINE:([Expr1013]=[Expr1013]))
                     |--Compute Scalar(DEFINE:([Expr1004]=[t1].[c2]+(1), [Expr1013]=CASE WHEN CASE WHEN ...
                           |--Top(ROWCOUNT est 0)
                                 |--Clustered Index Seek(OBJECT:([t1].[cidx]), SEEK:([t1].[c1]=[@p1]) ORDERED FORWARD)

   通過聚集索引的seek找到了一行,然后開始更新。這里注意的是,update的時候,它會申請一個針對clustered index的X鎖的。

   實際上到這里,我們就明白了為什么update會對select產生死鎖。update的時候,會申請一個針對clustered index的X鎖,這樣就阻塞住了(注意,不是死鎖!)select里面最后的那個clustered index seek。死鎖的另一半在哪里呢?注意我們的select語句,c2存在於索引idx1中,c1是一個聚集索引cidx。問題就在這里!我們在p2中更新了c2這個值,所以sqlserver會自動更新包含c2列的非聚集索引:idx1。而idx1在哪里?就在我們剛才的select語句中。而對這個索引列的更改,意味着索引集合的某個行或者某些行,需要重新排列,而重新排列,需要一個X鎖。
   SO………,問題就這樣被發現了。

   總結一下,就是說,某個query使用非聚集索引來select數據,那么它會在非聚集索引上持有一個S鎖。當有一些select的列不在該索引上,它需要根據rowid找到對應的聚集索引的那行,然后找到其他數據。而此時,第二個的查詢中,update正在聚集索引上忙乎:定位、加鎖、修改等。但因為正在修改的某個列,是另外一個非聚集索引的某個列,所以此時,它需要同時更改那個非聚集索引的信息,這就需要在那個非聚集索引上,加第二個X鎖。select開始等待update的X鎖,update開始等待select的S鎖,死鎖,就這樣發生鳥。

   那么,為什么我們增加了一個非聚集索引,死鎖就消失鳥?我們看一下,按照上文中自動增加的索引之后的執行計划:
   SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
      |--Index Seek(OBJECT:([deadlocktest].[dbo].[t1].[_dta_index_t1_7_2073058421__K2_K1_3]), SEEK:([deadlocktest].[dbo].[t1].[c2] >= [@p1] AND [deadlocktest].[dbo].[t1].[c2] <= [@p1]+(1)) ORDERED FORWARD)

   哦,對於clustered index的需求沒有了,因為增加的覆蓋索引已經足夠把所有的信息都select出來。就這么簡單。

   實際上,在sqlserver 2005中,如果用profiler來抓eventid:1222,那么會出現一個死鎖的圖,很直觀的說。

   下面的方法,有助於將死鎖減至最少(詳細情況,請看SQLServer聯機幫助,搜索:將死鎖減至最少即可。

按同一順序訪問對象。 
避免事務中的用戶交互。 
保持事務簡短並處於一個批處理中。 
使用較低的隔離級別。 
使用基於行版本控制的隔離級別。 
將 READ_COMMITTED_SNAPSHOT 數據庫選項設置為 ON,使得已提交讀事務使用行版本控制。 
使用快照隔離。
使用綁定連接。

---------------------------

轉自:http://blog.csdn.net/huwei2003/article/details/4047191


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM