SQL Server中的SELECT會阻塞SELECT嗎
前言
在SQL Server中,我們知道一個SELECT語句執行過程中只會申請一些意向共享鎖(IS) 與共享鎖(S),例如我使用SQL Profile跟蹤會話86執行SELECT * FROM dbo.TEST WHERE OBJECT_ID =1 這個查詢語句,其申請、釋放的鎖資源的過程如下所示:
而且從最常見的鎖模式的相容性表,我們可以看到IS鎖與S鎖都是相容的,也就是說SELECT查詢是不會阻塞SELECT查詢的。
現有的授權模式 |
||||||
請求的模式 |
IS |
S |
U |
IX |
SIX |
X |
意向共享(IS) |
是 |
是 |
是 |
是 |
是 |
否 |
共享(S) |
是 |
是 |
是 |
否 |
否 |
否 |
更新(U) |
是 |
是 |
否 |
否 |
否 |
否 |
意向排他(IX) |
是 |
否 |
否 |
是 |
否 |
否 |
意向排他共享(SIX) |
是 |
否 |
否 |
否 |
否 |
否 |
排他(X) |
否 |
否 |
否 |
否 |
否 |
否 |
但是在某些特殊場景。你會看到SELECT語句居然“阻塞”SELECT操作,那麼SQL Server中SELECT會真的阻塞SELECT操作嗎?我們先構造測試的案例場景,那麼先準備測試資料吧
CREATE TABLE TEST (OBJECT_ID INT,NAME VARCHAR(8)); CREATE INDEX PK_TEST ON TEST(OBJECT_ID) DECLARE @Index INT =0; WHILE @Index < 20 BEGIN INSERT INTO TEST SELECT @Index,'kerry'; SET @Index = @Index +1; END
在會話視窗A中,執行下面SQL語句,模擬一個UPDATE語句正在執行
BEGIN TRANSACTION UPDATE dbo.TEST SET NAME='Kerry' WHERE OBJECT_ID=1; --ROLLBACK;
會話視窗B中,執行下面的SQL語句
SELECT * FROM dbo.TEST WHERE OBJECT_ID=1
會話視窗C中,執行下面的SQL語句
SELECT * FROM dbo.TEST WHERE OBJECT_ID=1
我實驗的場景下,會話視窗A的會話ID為85,會話視窗B的會話ID為90,會話視窗C的會話ID為87,如下所示
如下所示,你會看到SELECT語句“阻塞”了SELECT語句,即會話90“阻塞”了會話87, 它們的等待事件都為LCK_M_S,也就是說它們都在等待獲取共享鎖,也許你會置疑這個SQL是否有問題,那麼我們使用SP_WHO來檢視,你會發現也是如此,如下所示:
如下所示,我們會發現會話ID為90 、87的會話都在等待型別為RID,Resource為1:24171:1的共享鎖
其實應該說,會話87、90都在等待RID物件的共享鎖,我們知道共享鎖與意向共享鎖都是相容的,所以SELECT是不會阻塞SELECT的,那麼又怎麼解釋這個現象呢?在宋大神的指點下,粗略的翻了Database System Implementaion這本書(很多原理性知識,看起來相當吃力)。裡面介紹了在鎖表(lock table)以及Element Info、Handling Lock Requests、Handling Unlocks等概念,有一個有意思的圖所示,
在鎖表(lock table)裡,elements info裡的鎖的申請是在一個類似佇列的結構。先進先出機制,所以當會話90先進入佇列,它在等待共享鎖(S),會話87也進入佇列等待共享鎖(S),而且它在會話90的後面(即會話90這個elements info後面的Next指標指向會話87會話的事務),由於兩個會話都被阻塞,這兩個會話的Wait欄位都是Yes,由於內部某些機制,會話87顯示阻塞它的會話為90(這個是我個人臆測,實際具體原因有待考究),實質阻塞的源頭還是會話85. 當會話85釋放排它鎖(X)後,會話佇列根據下面幾個原則來處理解鎖(Handling Unlocks):
1: First-come-first-served: Grant the lock request that has been waiting the longest. This strategy guarantees no starvation,the situation where a transaction can wait forever for a lock
先來先服務(佇列的原則):授予鎖等待時間最長的鎖請求,這種策略保證不會餓死(翻譯感覺不貼切),即一個事務不會永遠等待鎖的情況。
2. Priority to shared locks: First grant all the shared locks waiting. Then,grant one update lock,if there are any waiting. Only grant an exclusive lock if no others are waiting. This strategy can allow starvation,if a transaction is waiting for a U or X lock.
共享鎖優先,首先授予所有等待共享鎖(S),然後授予其中一個更新鎖(U),如果有其它型別等待,只有在沒有其它鎖等待時,才授予排它鎖、這一策略允許等待更新鎖或排它鎖的事務餓死(結束)
3. Priority to upgrading: If there is a transaction with a U lock waiting to upgrade it to an X lock,grant that first. Otherwise,follow one of the other strategies mentioned.
鎖升級優先,如果有一個持有共享鎖(U)等待升級Wie排他鎖(X),那麼先授予它排它鎖,否則採用前面已經提到的策略中的一個。
按照這些原則,當會話85釋放了排它鎖(X)後,排程器(Scheduler)應該會根據先後順序依次授予會話90、87共享鎖(S),兩者的阻塞會幾乎同時消失。 這個可以也可以通過實驗進行一個大概的推斷, 在上面實驗中,你可以手工取消90會話的查詢操作,然後再檢視阻塞情況,就會發現會話87被85阻塞了。這個阻塞的源頭就變成了85,而不是90了。
PS:上面是個人結合一些知識和理解,做的一些膚淺的判斷與分析,如果不對的地方,敬請指正!
參考資料:
Database System Implementaion
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對我們的支援。