SQLSERVER臨時表引起的"鎖"案

在一個CS結構的項目裏使用SQLServer時碰到一個有意思的現象,如下是從日誌中摘出來的用戶操做:數據庫


用戶A的操做會引起程序在事務中使用Local臨時表,例如:緩存

1 BEGIN TRAN
2 
3 SELECT * INTO #temp FROM DB1.dbo.Table1
4 
5 --do something
6 
7 DROP TABLE #temp
8 
9 COMMIT TRAN

用戶B的操做也會引起程序使用臨時表且在刪除臨時表前會先判斷臨時表的存在性,例如:服務器

1 SELECT * INTO #MyTempTable FROM TestJJV9.dbo.test
2 
3 IF EXISTS(SELECT 1 FROM tempdb.dbo.sysobjects WHERE name like '%#MyTempTable%')
4 BEGIN
5 DROP TABLE tempdb.dbo.#MyTempTable
6 END

用戶A和用戶B使用的是同一個SQLServer實例,可是用的是不一樣的數據庫。session

 

那麼,現象來了,用戶A的操做越頻繁,用戶B越容易碰到長時間的等待甚至超時。spa

 

分析
從用戶B的操做來看,判斷臨時表存在性的SQL語句(line3)致使了對tempdb.dbo.sysobjects的掃描(sysobjects是一個view,實際上掃描的是sys.sysschobjs的彙集索引),該掃描須要對sys.sysschobjs的彙集索引伸請S鎖。
而用戶A的操做會對sys.sysschobjs表的彙集索引的KEY持有一個X鎖,在用戶A的事務提交以前,用戶B沒法得到S鎖,因此處於等待狀態。
若是有C,D,E等用戶持續在執行相似用戶A的操做,那麼用戶B基本上就只能等死了。。。日誌

解決方案
使用OBJECT_ID判斷臨時表的存在性,能夠避免對sys.sysschobjs的掃描,防止被鎖(猜測其內部多是從相似緩存的結構中取得的結果),例如:code

SELECT * INTO #MyTempTable FROM DB2.dbo.AnyTable

IF OBJECT_ID('tempdb.dbo.#MyTempTable') IS NOT NULL    
BEGIN
DROP TABLE tempdb.dbo.#MyTempTable
END

 

另外,上述用戶A的操做不管生成的是Local臨時表仍是Global臨時表,都有這個現象,緣由是同樣的。blog

最後轉載一個即時調查當前服務器情況的SP,要調查相似上述問題時,能夠以下使用:索引

exec sp_WhoIsActive @get_locks = 1, @find_block_leaders = 1

從blocking_session_id, blocked_session_count, locks列能夠看出誰正在被誰阻塞事務

相關文章
相關標籤/搜索