SQL2005中的事務與鎖定(五)
------------------------------------------------------------------------
-- Author : HappyFlyStone
-- Date : 2009-10-05 14:00:00
-- Version: Microsoft SQL Server 2005 - 9.00.2047.00 (Intel X86)
-- Apr 14 2006 01:12:25
-- Copyright (c) 1988-2005 Microsoft Corporation
-- Enterprise Edition on Windows NT 5.2 (Build 3790: Service Pack 2)
-- 轉載請註明出處,更多請關注:http://blog.csdn.net/happyflystone
-- 關鍵字:鎖定 行版本控制 Lock Hint HOLDLOCK
------------------------------------------------------------------------
在生產交易過程中多個用戶同時訪問數據是不可以避免的,通過不同的隔離等級對資源與數據進行各種類型的鎖定保護並在適當時候釋放保證交易的正確運行,使得交易完整並保證數據的一致性。不管是鎖定還是行版本控制器都決定着商業邏輯的流暢、事務的完整、數據的一致。所以我們要根據實際情況進行部署,在併發性性能與資源管理成本之間找到平衡點,怎樣才能找到這個平衡點呢,那我們就得對SQLSERVER如何管理資源與鎖有一個瞭解,SQLSERVER不但管理鎖定,還要管理鎖定模式之間的兼容性或升級鎖定及解決死鎖問題。通過SQL SERVER強大的、細緻的鎖定機制,使得併發性能得到最大程度的發揮,但是使用儘可能少的系統資源也是我們最希望的。
SQLSERVER本身有兩種鎖定體系:一種是對共享數據的鎖定,這種鎖定就是我們大部時間討論的鎖定;一種是對內部數據結構及處理索引,這是一種稱爲閂鎖的輕量級鎖,比第一種鎖定少耗資源,在sys.dm_tran_locks中是看不到這種鎖的信息。我們在數據分頁上放置物理記錄或壓縮、折分、轉移分頁數據時,這種鎖就會發生了。我們在前面一直在說數據的邏輯一致性,那這種邏輯上的一致性就是通過鎖定來控制的,而我們新提到的閂是保證物理的一致性(這種閂是系統內部使用所以我們不重點討論了)。
併發訪問數據時,SQL Server 2005使用下列機制確保事務完整並維護數據的一致性:
l 鎖定
每個事務對所依賴的資源(如行、頁或表)請求不同類型的鎖。鎖可以阻止其他事務以某種可能會導致事務請求鎖出錯的方式修改資源。當事務不再依賴鎖定的資源時,它將釋放鎖。
l 行版本控制
當啓用了基於行版本控制的隔離級別時,數據庫引擎 將維護修改的每一行的版本。應用程序可以指定事務使用行版本查看事務或查詢開始時存在的數據,而不是使用鎖保護所有讀取。通過使用行版本控制,讀取操作阻止其他事務的可能性將大大降低。
鎖定和行版本控制可以防止用戶讀取未提交的數據,還可以防止多個用戶嘗試同時更改同一數據。如果不進行鎖定或行版本控制,對數據執行的查詢可能會返回數據庫中尚未提交的數據,從而產生意外的結果。
最後說一下鎖的粒度與併發性能是矛盾的,但是對管理鎖定的成本卻是有利的,粒度越大併發性能下降,粒度越小管理鎖定成本越大。用圖示例一下:
六、鎖定
1、鎖粒度和可鎖定資源
SQL Server2005 具有多粒度鎖定,允許一個事務鎖定不同類型的資源。爲了儘量減少鎖定的開銷,數據庫引擎自動將資源鎖定在適合任務的級別。鎖定在較小的粒度(例如行)可以提高併發度,但開銷較高,因爲如果鎖定了許多行,則需要持有更多的鎖。鎖定在較大的粒度(例如表)會降低了併發度,因爲鎖定整個表限制了其他事務對錶中任意部分的訪問,但其開銷較低,因爲需要維護的鎖較少。
SQL SERVER可以鎖定表、分頁、行級、索引鍵或範圍。在這我提醒大家一下,對於聚集索引的表,因爲數據行就是索引的葉級,所以鎖定是鍵鎖完成而不是行鎖。
數據庫引擎通常必須獲取多粒度級別上的鎖才能完整地保護資源。這組多粒度級別上的鎖稱爲鎖層次結構。例如,爲了完整地保護對索引的讀取,數據庫引擎實例可能必須獲取行上的共享鎖以及頁和表上的意向共享鎖。
下表列出了數據庫引擎可以鎖定的資源:
查詢一:
SELECT *
FROM MASTER..SPT_VALUES WHERE TYPE = 'LR'
/*
name number type low high status
--------------- ----------- ---- ------- --------- -----------
LOCK RESOURCES 0 LR NULL NULL 0
NUL 1 LR NULL NULL 0
DB 2 LR NULL NULL 0
FIL 3 LR NULL NULL 0
TAB 5 LR NULL NULL 0
PAG 6 LR NULL NULL 0
KEY 7 LR NULL NULL 0
EXT 8 LR NULL NULL 0
RID 9 LR NULL NULL 0
APP 10 LR NULL NULL 0
MD 11 LR NULL NULL 0
HBT 12 LR NULL NULL 0
AU 13 LR NULL NULL 0
(13 行受影響)
*/
備註:
RID RID 鎖定堆中行的行標識符
KEY KEY 序列化事務中的鍵範圍行鎖
PAG PAGE 數據或索引頁面,8K爲單位
EXT EXTENT 數據或索引頁面,連續的8*page
HBT HOBT 堆或B樹,保護索引或堆表頁堆的鎖
TAB TABLE 整個表,包括數據及索引
FIL FILE 數據庫文件
APP APPLICATION 應用程序資源
MD METADATA 元數據
AU ALLOCATION_UNIT 分配單元
DB DATABASE 數據庫
注:SPT_VALUES這個大家不陌生吧,好多人用它生成一個連續的ID號的啦,當時也有人問這個表的用途,現在發現它的作用了吧。下面我們還會使用到。
2、鎖定模式
我們在前提面前到的共享鎖定、更新鎖定、排它鎖定,這是爲了配合前面的事務而提及的,那麼SQL SERVER2005一共有多少鎖定模式呢?我們通過一個簡單的查詢來列表:
查詢:
SELECT *
FROM MASTER..SPT_VALUES WHERE [TYPE] = 'L'
/*
NAME NUMBER TYPE LOW HIGH STATUS
---------------- ----------- ---- ----------- ----------- -----------
LOCK TYPES 0 L NULL NULL 0
NULL 1 L NULL NULL 0
SCH-S 2 L NULL NULL 0
SCH-M 3 L NULL NULL 0
S 4 L NULL NULL 0
U 5 L NULL NULL 0
X 6 L NULL NULL 0
IS 7 L NULL NULL 0
IU 8 L NULL NULL 0
IX 9 L NULL NULL 0
SIU 10 L NULL NULL 0
SIX 11 L NULL NULL 0
UIX 12 L NULL NULL 0
BU 13 L NULL NULL 0
RANGES-S 14 L NULL NULL 0
RANGES-U 15 L NULL NULL 0
RANGEIN-NULL 16 L NULL NULL 0
RANGEIN-S 17 L NULL NULL 0
RANGEIN-U 18 L NULL NULL 0
RANGEIN-X 19 L NULL NULL 0
RANGEX-S 20 L NULL NULL 0
RANGEX-U 21 L NULL NULL 0
RANGEX-X 22 L NULL NULL 0
(23 行受影響)
*/
我們可以看到一共有22種鎖定模式 ,我簡單的對上述[NAME]進行簡單的枚舉:
l S --- 共享鎖定(Shared)
l U --- 更新鎖定(Update)
l X --- 排它鎖定(Exclusive)
l I --- 意向鎖定(Intent)
l Sch --- 架構鎖定(Schema)
l BU --- 大量更新(Bulk Update)
l RANGE --- 鍵範圍(Key-Range)
l 其它是在上述鎖定的變種組合,比如IS --- 意向共享鎖定
其實對這些鎖定模式沒什麼介紹,大家可以參考聯機幫助:訪問和更改數據庫數據 -> 鎖定和行版本控制 -> 數據庫引擎中的鎖定。其實這些鎖定模式在前一篇基本都有出現,大家可以在看下面的定義再回頭看看前一篇的相關內容。下面我就簡單的說說:
共享鎖(S 鎖)
當我們查詢(select)數據時SQL SERVER2005會嘗試在數據上申請共享鎖定,但是前提是在當前的數據上不存在與共享鎖定互斥的鎖定。資源上存在共享鎖時,任何其他事務都不能修改數據但是可以讀取數據。讀取操作一完成,就立即釋放資源上的共享鎖,除非將事務隔離級別設置爲可重複讀或更高級別,或者在事務持續時間內用鎖定提示(HOLDLOCK)保留共享鎖。
更新鎖(U 鎖)
更新新是一種介於共享鎖與排它鎖之間的鎖定,是一種中繼鎖定,像一箇中間閘門,把從共享鎖定轉爲排它鎖的請求進行排隊,有效的防止常見的死鎖。在可重複讀或可序列化事務中,一個事務讀取數據 [獲取資源(頁或行)的共享鎖(S 鎖)],然後修改數據 [此操作要求鎖轉換爲排他鎖(X 鎖)]。如果兩個事務獲得了資源上的共享模式鎖,然後試圖同時更新數據,則一個事務嘗試將鎖轉換爲排他鎖(X 鎖)。共享模式到排他鎖的轉換必須等待一段時間,因爲一個事務的排他鎖與其他事務的共享模式鎖不兼容;發生鎖等待。第二個事務試圖獲取排他鎖(X 鎖)以進行更新。由於兩個事務都要轉換爲排他鎖(X 鎖),並且每個事務都等待另一個事務釋放共享模式鎖,因此發生死鎖。而有了更新鎖則可避免這種潛在的死鎖問題,在查找到要更新的數據後SQL SERVER首先給數據設置更新鎖定,因爲共享鎖定與更新鎖定不互斥,在其它事務設置共享鎖定時依然可以設置更新鎖定,繼而因更新鎖定斥的,如果其它要修改數據的事務必須等待。如果事務修改資源,則更新鎖轉換爲排他鎖(X 鎖)。
排他鎖(X 鎖)
排他鎖可以防止併發事務對資源進行訪問。使用排他鎖(X 鎖)時,任何其他事務都無法修改數據;僅在使用 NOLOCK 提示或未提交讀隔離級別時纔會進行讀取操作。
數據修改語句(如 INSERT、UPDATE 和 DELETE)合併了修改和讀取操作。語句在執行所需的修改操作之前首先執行讀取操作以獲取數據。因此,數據修改語句通常請求共享鎖和排他鎖。例如,UPDATE 語句可能根據與一個表的聯接修改另一個表中的行。在此情況下,除了請求更新行上的排他鎖之外,UPDATE 語句還將請求在聯接表中讀取的行上的共享鎖。
排他鎖定隨事務結束而釋放。
意向鎖(I鎖)
數據庫引擎使用意向鎖來保護共享鎖(S 鎖)或排他鎖(X 鎖)放置在鎖層次結構的底層資源上。意向鎖之所以命名爲意向鎖,是因爲在較低級別鎖前可獲取它們,因此會通知意向將鎖放置在較低級別上。
意向鎖有兩種用途:
l 防止其他事務以會使較低級別的鎖無效的方式修改較高級別資源。
l 提高數據庫引擎 在較高的粒度級別檢測鎖衝突的效率。
例如,在該表的頁或行上請求共享鎖(S 鎖)之前,在表級請求共享意向鎖。在表級設置意向鎖可防止另一個事務隨後在包含那一頁的表上獲取排他鎖(X 鎖)。意向鎖可以提高性能,因爲數據庫引擎僅在表級檢查意向鎖來確定事務是否可以安全地獲取該表上的鎖。而不需要檢查表中的每行或每頁上的鎖以確定事務是否可以鎖定整個表。
意向鎖包括意向共享 (IS)、意向排他 (IX)、意向排他共享 (SIX)、意向更新 (IU)、共享意向更新 (SIU ,S和 IU 鎖的組合)、更新意向排他 (UIX,U 鎖和 IX 鎖的組合)。
在這兒的SIX,SIU,UIX我們可以理解成一種轉換鎖定,並不是由SQLSERVER直接申請的,是由一種模式向另一種模式轉換時中間狀態。比如說SIX表示一種正持有共享鎖定的進程正在企圖申請意向排它鎖定,或是這樣理解一個持有共享鎖定的資源中有部分分頁或行被另一個進程的排它鎖定鎖定了。其它同理可以理解。
爲了更好的說明一點, 大家先看一個圖:
這是我在TA表上加Where條件的一個更新動作,然後通過我以前寫的一個工具:sp_us_lockinfo查看鎖的信息,其實我的update只是影響一個行記錄,但是我們發現有三個鎖存在,只要當前事務不結束,其它事物對這個表申請不管是頁面的鎖定還是表級的鎖定一定會與現在的表或頁意向鎖衝突,進而發生阻塞,而且我們在前面的隔離等級的實例中也有例子,你會發現它的請求狀態是WAIT 而不是GRANT。
架構鎖(架構修改鎖 Sch-M 鎖、架構穩定性鎖Sch-S 鎖)
執行表的數據定義語言 (DDL) 操作(例如添加列或刪除表)時使用架構修改鎖。在架構修改鎖起作用的期間,會防止對錶的併發訪問。這意味着在釋放架構修改鎖(Sch-M 鎖)之前,該鎖之外的所有操作都將被阻止。
當編譯查詢時,使用架構穩定性鎖。架構穩定性鎖不阻塞任何事務鎖,包括排他鎖(X 鎖)。因此在編譯查詢時,其他事務 [包括在表上有排他鎖(X 鎖)的事務] 都能繼續運行。但不能在表上執行 DDL 操作。
大容量更新鎖(BU 鎖)
當將數據大容量複製到表,且指定了 TABLOCK 提示或者使用 sp_tableoption 設置了 table lock on bulk 表選項時,將使用大容量更新鎖。大容量更新鎖允許多個線程將數據併發地大容量加載到同一表,同時防止其他不進行大容量加載數據的進程訪問該表。
鍵鎖、鍵範圍鎖(Key-range鎖)
在SQL SERVER2005有兩種類型鍵鎖:鍵鎖及鍵範圍鎖。採用哪種類型的鍵鎖取決於隔離級別。對於已提交讀、可重複讀、快照隔離時SQLSERVER鎖定實際的索引鍵(如果是堆表除了實際非聚集索引上的鍵鎖同時有實際行上的行鎖),如果是可串行化隔離時就可以看到鍵範圍鎖。在早期的版本中我們實驗可以看到SQLSERVER是通過分頁鎖定或表鎖來實現的,也許鍵範圍鎖不是最完美的,但是我們應該看到它比分頁或表鎖定所鎖定的範圍要小得多,在保證不出現幻影的前提下鍵範圍鎖比以前版本採用鎖定提供了更高的併發性能。
鍵範圍鎖放置在索引上,指定開始鍵值和結束鍵值。此鎖將阻止任何要插入、更新或刪除任何帶有該範圍內的鍵值的行的嘗試,因爲這些操作會首先獲取索引上的鎖。鍵範圍鎖包括按範圍-行格式指定的範圍組件和行組件,是一種組合鎖模式(Range範圍-索引項的鎖模式)。比如:RangeI-N ,RangeI 表示插入範圍,N(NULL) 表示空資源,它表示在索引中插入新鍵之前測試範圍。
在SELECT * FROM MASTER..SPT_VALUES WHERE [TYPE] = 'L'查詢結果的最後9條個就是鍵範圍鎖。這種鎖定因持續時間比較短一般在sys.dm_tran_locks中很難見到。比如RangeI_N這個鎖定,是在鍵範圍內插入記錄時獲得的,在鍵範圍內找到位置立即升級爲X鎖定,這個過程很短,我們在sys.dm_tran_locks中很難找到它的蹤影,不過我們是可以模擬出來的,下面我們來模擬一下:
查詢一:
DROP TABLE TB
GO
CREATE TABLE TB (ID INT primary key, COL VARCHAR(16))
GO
INSERT INTO TB SELECT 1,'A'
GO
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN
SELECT * FROM TB WHERE id BETWEEN 1 AND 5 --OLD數據
--COMMIT TRAN --Don't commit
SELECT @@SPID
/*
(1 行受影響)
ID COL
----------- ----------------
1 A
(1 行受影響)
------
52
(1 行受影響)
*/
查詢二:
INSERT TB SELECT 2,'E'
查詢三:
exec sp_us_lockinfo
在使用可序列化事務隔離級別時,對於 Transact-SQL 語句讀取的記錄集,鍵範圍鎖在索引獲上取鎖定阻止一切嘗試在包含索引鍵值落入範圍內增刪改的數據行,可以隱式保護該記錄集中包含的行範圍。鍵範圍鎖可防止幻讀。通過保護行之間鍵的範圍,它還防止對事務訪問的記錄集進行幻像插入或刪除。
例如我們在上面有例子的可串行化隔離級別下,選擇索引鍵值在’1-5’的數據時,SQL SERVER 對落在1-5之間鍵值設置鍵範圍鎖定,避免包含在這個範圍內的鍵值的插入及這個範圍內鍵值的刪除及更新。
最後強調一下鍵範圍鍵產生的條件:
1、 務隔離級別必須設置爲 SERIALIZABLE。
2、詢處理器必須使用索引來實現範圍篩選謂詞。例如,SELEC中的 WHERE 子句。
3、鎖兼容性矩陣
鎖兼容性控制多個事務能否同時獲取同一資源上的鎖。如果資源已被另一事務鎖定,則僅當請求鎖的模式與現有鎖的模式相兼容時,纔會授予新的鎖請求。如果請求鎖的模式與現有鎖的模式不兼容,則請求新鎖的事務將等待釋放現有鎖或等待鎖超時間隔過期。
請大家繼續關注我的blog: http://blog.csdn.net/happyflystone