鎖定是 SQL Server 數據庫引擎用來同步多個用戶同時對同一個數據塊的訪問的一種機制。html
基本概念sql
隔離級別 | 定義 |
---|---|
未提交的讀取 | 隔離事務的最低級別,只能保證不讀取物理上損壞的數據。 在此級別上,容許髒讀,所以一個事務可能看見其餘事務所作的還沒有提交的更改 |
已提交的讀取 | 容許事務讀取另外一個事務之前讀取(未修改)的數據,而沒必要等待第一個事務完成。 SQL Server 數據庫引擎保留寫鎖(在所選數據上獲取)直到事務結束,可是一執行 SELECT 操做就釋放讀鎖。 這是SQL Server 數據庫引擎默認級別 |
可重複的讀取 | SQL Server 數據庫引擎保留在所選數據上獲取的讀鎖和寫鎖,直到事務結束。 可是,由於無論理範圍鎖,可能發生虛擬讀取 |
可序列化 | 隔離事務的最高級別,事務之間徹底隔離。 SQL Server 數據庫引擎保留在所選數據上獲取的讀鎖和寫鎖,在事務結束時釋放它們。 SELECT 操做使用分範圍的 WHERE 子句時獲取範圍鎖,主要爲了不虛擬讀取 |
資源 | 說明 |
---|---|
RID | 用於鎖定堆中的單個行的行標識符,也就是常說的行鎖 |
KEY | 索引中用於保護可序列化事務中的鍵範圍的行鎖 |
PAGE | 數據庫中的 8 KB 頁,例如數據頁或索引頁,也就常說的業級鎖 |
EXTENT | 一組連續的八頁,例如數據頁或索引頁 |
HoBT | 堆或 B 樹。 用於保護沒有彙集索引的表中的 B 樹(索引)或堆數據頁的鎖 |
TABLE | 包括全部數據和索引的整個表 |
FILE | 數據庫文件 |
APPLICATION | 應用程序專用的資源 |
METADATA | 元數據鎖 |
ALLOCATION_UNIT | 分配單元 |
DATABASE | 整個數據庫 |
鎖 | 說明 |
---|---|
共享 (S) | 用於不更改或不更新數據的讀取操做,如 SELECT 語句 |
更新 (U) | 用於可更新的資源中。 防止當多個會話在讀取、鎖定以及隨後可能進行的資源更新時發生常見形式的死鎖 |
排他 (X) | 用於數據修改操做,例如 INSERT、UPDATE 或 DELETE。 確保不會同時對同一資源進行多重更新 |
意向 (I) | 用於創建鎖的層次結構。 意向鎖包含三種類型:意向共享 (IS)、意向排他 (IX) 和意向排他共享 (SIX) |
架構 (Sch-) | 在執行依賴於表架構的操做時使用。 架構鎖包含兩種類型:架構修改 (Sch-M) 和架構穩定性 (Sch-S) |
大容量更新 (BU) | 在將數據大容量複製到表中且指定了 TABLOCK 提示時使用 |
鍵範圍 (Range) | 當使用可序列化事務隔離級別時保護查詢讀取的行的範圍。 確保再次運行查詢時其餘事務沒法插入符合可序列化事務的查詢的行 |
利用SQL Server Profiler觀察鎖數據庫
1. 準備數據10條數據架構
create table DataTable(Id int identity(1,1), [Name] varchar(50), [Address] varchar(200), CreateTime datetime2) insert into DataTable select 'Wilson','廣東省廣州市',GETDATE() union all select 'Alice','北京市朝陽區',GETDATE() union all select 'Miksovsky','吉林省松原市',GETDATE() union all select 'Hines','甘肅省蘭州市',GETDATE() union all select 'Kane','遼寧省瀋陽市',GETDATE() union all select 'Gode','湖北省荊州市',GETDATE() union all select 'Chen','湖南省岳陽市',GETDATE() union all select 'Trenary','福建省廈門市',GETDATE() union all select 'Achong','廣西省玉林市',GETDATE() union all select 'Nixon','江西省景德鎮',GETDATE()
2. 打開SQL Server Profiler選中鎖事件,勾選type和mode,建議取消不須要觀察的列,而後用列篩選器過濾要觀察的DB併發
3. 查詢數據ide
能夠看到在頁面級別加上意向共享鎖,由於咱們數據只有一頁ui
4. 更新一條數據編碼
1. 表上加上意向排它鎖(IX),能夠用select OBJECT_NAME(581577110) 查看objectid表明的東西spa
2. 頁級別加上意向更新鎖(IU),告訴SQL Server引擎這裏有更新鎖3d
3. 獲取第一行的更新鎖(U),這裏條件匹配
4. 頁級別升級爲意向排他鎖(IX), 告訴SQL Server引擎這裏有排他鎖
5. 第一個行更新鎖 升級爲排它鎖(X)
6. 釋放鎖
7. 隨條掃描後面的記錄,只是條件不符合,也就不會升級鎖級別
能夠看到是全表掃描,由於沒彙集索引(堆表),咱們也沒作一個主鍵,下面將Id添加主鍵而後再更新試試
alter table DataTable add constraint PK_DataTable primary key(Id asc)
能夠看出,直接在表,頁級別加上意向排它鎖(IX),而後在鍵上加上排它鎖(X)
由於這裏咱們用主鍵更新,並且SQL Server主鍵默認是彙集索引,若是指定是非彙集索引主鍵,這裏也會經歷更新鎖 到 排他鎖,有興趣的能夠自行驗證
5. 刪除一條數據
此次咱們沒用主鍵刪除,過程和更新的第一種狀況差很少,就不列了。
由於加了彙集索引,索引定位器執行彙集索引Key的hash,要驗證是否那條記錄,能夠在刪除前加上%%lockres%%去查
死鎖產生的緣由及避免
微軟文檔是這樣說
在兩個或多個任務中,若是每一個任務鎖定了其餘任務試圖鎖定的資源,此時會形成這些任務永久阻塞,從而出現死鎖
我理解就是有2個事務循環依賴對方的資源致使產生死鎖。
例如
1. 事務A 獲取 Row1 資源
2. 事務B 獲取 Row2 資源
3. 事務A獲取Row2資源,因爲這時Row2是被事務B佔有,因此必須等事務B完成
4. 事務B獲取Row1資源,因爲這時Row1是被事務A佔有,因此必須等事務A完成
1. 按期檢查陷入死鎖的任務
2. 若檢查到循環依賴
3. 選擇其中一個做爲犧牲品,而後終止事務,然另一個得以完成
分別在兩個不一樣的會話執行下面語句
begin tran; update DataTable set Address = '上海市' where Id = 2; --延遲5秒執行 WAITFOR DELAY '00:00:05'; update DataTable set Address = '上海市' where Id = 3; commit;
begin tran; update DataTable set Address = '上海市' where Id = 3; --延遲5秒執行 WAITFOR DELAY '00:00:05'; update DataTable set Address = '上海市' where Id = 2; commit;
執行一段時間,其中一個會出現下面錯誤
打開Locks事件的死鎖圖形
從新執行上面語句,模擬死鎖,Profiler捕獲到死鎖
能夠看出
1. 進程56 請求的Key 的排它鎖 被進程 54 佔有
2. 進程54 請求的Key 的排他鎖 被進程 56 佔有
3. 造成了循環依賴
咱們這裏的Sql比較簡單,並且沒有用參數化執行,因此咱們指定是哪一行被鎖,線上的一般不能直接看到哪一行被鎖
咱們能夠經過xml查看等待的資源,在xml裏面有process-list 下面有多個process,process節點上面有個waitresource屬性,這個指出每一個進程等待的資源
鎖類型:db_id : hobt_id : (hashvalue)
KEY: 6:72057594043760640 (61a06abd401c)
經過%%lockres%% 查到被鎖資源
select %%lockres%%,* from DataTable where %%lockres%% = '(98ec012aa510)'
鎖類型不同,獲得的會不同,根據各自的格式用db_name / object_name / dbcc去查到當前被鎖的資源,有時候須要利用DBCC查詢Page存儲頁面,能夠參考上一篇文章【SQL SERVER】數據內部存儲結構簡單探索
首先須要說明死鎖不能徹底避免,但遵照特定的編碼慣例能夠將發生死鎖的機會降至最低
1. 按同一順序訪問對象,一個獲取鎖,另一個就必須等待
2. 避免事務中的用戶交互 ,這樣致使事務時間過長,容易形成死鎖
3. 保持事務簡短並處於一個批處理中,道理和2同樣,儘可能讓事務運行時間短。
4. 使用較低的隔離級別,這個看能不能接受髒讀,幻讀等反作用
總結
1. 鎖機制保證併發狀況下的數據訪問。
2. 開發中應該儘可能利用索引檢索數據,特別是UPDATE/DELETE這種須要排它鎖,應該利用惟一彙集索引字段更新(一般是主鍵)
3. 規範使用事務能減小死鎖發生
轉發請標明出處:https://www.cnblogs.com/WilsonPan/p/12618849.html