這篇博文簡單介紹一下在SQL Server中一條Insert語句中用到的鎖。
准備數據
首先我們建立一張表Table_1,它有兩列Id(bigint)和Value(varchar),其中Id建立了主鍵。

CREATE TABLE [dbo].[Table_2]( [Id] [bigint] NOT NULL, [Value] [nchar](10) NULL, CONSTRAINT [PK_Table_2] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
然后插入兩條數據。
insert into dbo.table_2 (id, value) values (1, '1'), (2, '2');
開始測試
我們知道,在Transaction中共享鎖在查詢語句結束就釋放了,而排它鎖則在Transaction提交才釋放。我們可以利用它來執行一個Insert,不提交Transaction,然后去查看鎖的狀態。注意,本文中查詢窗口配置的Transaction隔離級別是默認值READ COMMITTED。
首先執行以下SQL:
begin tran t1 insert into dbo.table_2 (id, value) values (3, '3');
然后查看鎖:
SELECT resource_type, request_mode, resource_description, request_session_id, request_status, resource_associated_entity_id, DB_NAME(resource_database_id)as resource_database FROM sys.dm_tran_locks WHERE resource_type <> 'DATABASE' ORDER BY request_session_id;
執行結果如下:
- 第一個是意向排他鎖。它表示這個數據頁下存在排他鎖(就是第三個排他鎖),我們發現它的resource_associated_entity_id和第三個鎖一樣。那么,這個數據頁就是存放這行數據的這個主鍵的。
- 第二個也是意向排他。它的resource_type是OBJECT,此對象可以是數據表、視圖、存儲過程、擴展存儲過程或任何具有對象 ID 的對象。它的resource_associated_entity_id這一列其實是object_id, 用函數object_name(object_id)看一下發現結果是Table_2。那么它下面存在的排他鎖指的也是第三個鎖了。
- 第三個是排他鎖。resou_description指的是插入數據主鍵的哈希值。
補充1
此時,我們在另外一個命令窗口中執行以下查詢語句不會產生阻塞:
SELECT * FROM dbo.Table_2 WHERE id=1;
但另一條卻會產生阻塞:
SELECT * FROM dbo.Table_2 WHERE id=3;
來看看第一條SQL產生的鎖。由於共享鎖會在查詢結束立即釋放,因此我們加一個HOLDLOCK,讓它在事務結束再釋放:
begin tran t2 SELECT * FROM dbo.Table_2 WITH(HOLDLOCK) WHERE id=1;
這是執行完以上語句鎖的情況:
第二條SQL會產生阻塞,因此可以直接查詢然后看鎖的情況:
我們發現第9行的resource_description和第3行是相同的,這也說明了主鍵的鎖只是鎖住了某一個值而已。
補充2
這條SQL也會被Insert阻塞:
SELECT value FROM dbo.Table_2 WHERE value='1'
而且查看當前的鎖可以發現,Key被鎖的值正是Insert語句的Key值。這里有兩個疑問:1. 為什么沒用到主鍵列,卻產生了主鍵鎖。2.為什么Insert的數據還未commit,這里卻會產生這一行主鍵的鎖。
答:1. 我們查看查詢計划,可以看到這條語句是用了聚集索引掃描,至於為什么不是表掃描,請看這里。 2. 由於事務隔離級別默認是Read Committed,所以這里會對已插入但未提交的數據主鍵加一個共享鎖。