最近有一個困惑,生產服務器上有一表索引建得亂七八糟,經過整理后需要新建幾個索引,再刪除幾個索引,建立索引時使用聯機(ONLINE=ON)創建,查看下服務器負載(磁盤和CPU壓力均比較低的情況)后就選擇業務時間創建,但是到刪除索引時卻遇到問題:阻塞,刪除索引需要架構修改鎖(SCH_M),有阻塞很正常,雖然查詢使用NOLOCK提示降低了對其他會話的影響,但還是會在頁或表上生成一些意向共享鎖(IS),這些意向共享鎖與SCH_M無法兼容,因此阻塞無可避免,悲催的是在該表上多個會話重復執行查詢且該查詢執行時間超過100秒,根本無法找到一個完美的時間空擋來執行刪除操作。想着白天業務高峰不成,我晚上來,為此好幾晚半夜爬起來做嘗試刪除操作,最后還是跟業務確認后使用KILL干掉所有長時間阻塞會話才得以刪除成功。
啰啰嗦嗦一堆,問題來了:在聯機創建索引時,同樣需要架構修改鎖(SCH_M),為什么這就不阻塞呢?
感謝群里大神“一川晴雨”提醒,聯機創建索引和刪除索引雖然都使用架構修改鎖(SCH_M),但是作用的對象卻是不同的,因此影響也不同,那就讓我們來驗證下吧
首先准備測試數據
--============================= --創建測試數據庫 CREATE DATABASE DB2 GO USE db2 GO --創建測試表 CREATE TABLE TB1004 ( ID INT IDENTITY(1,1) PRIMARY KEY, C1 BIGINT ) GO --導入數據,本次測試導入100w數據 INSERT INTO TB1004() SELECT OBJECT_ID FROM SYS.all_columns go 2000 --查詢導入的數據量 SELECT COUNT(1) FROM TB1004
接下來就是准備抓起鎖,我們使用XEVENT來完成
--創建擴展回話XE_LockMonitor --增加監控事件sqlserver.lock_acquired和sqlserver.lock_released --並按鎖類型和數據庫名來過濾數據 CREATE EVENT SESSION [XE_LockMonitor] ON SERVER ADD EVENT sqlserver.lock_acquired( ACTION(sqlserver.database_id,sqlserver.database_name,sqlserver.sql_text) WHERE ([sqlserver].[equal_i_sql_unicode_string]([sqlserver].[database_name],N'DB2') AND [mode]=(2))), ADD EVENT sqlserver.lock_released( ACTION(sqlserver.database_id,sqlserver.database_name,sqlserver.sql_text) WHERE ([sqlserver].[equal_i_sql_unicode_string]([sqlserver].[database_name],N'DB2') AND [mode]=(2))) ADD TARGET package0.event_file(SET filename=N'D:\DB\XE_LockMonitor.xel') WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=30 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=OFF,STARTUP_STATE=OFF) GO
建立完后查看該擴展事件屬性
有了擴展事件會話,然后我們激活啟用它
--============================================================= --啟動回話 ALTER EVENT SESSION [XE_LockMonitor] ON SERVER STATE=START;
啟動擴展會話后,選擇“監視實時數據”,然后在彈出的窗口中,先配置要顯示的數據,在標題欄右鍵選擇“選擇列”,選擇以下我們關心的列,並保存。
准備好測試環境,是時候開測數據啦
--======================== --脫機創建索引 --耗時5秒 CREATE INDEX IDX_OBJECTID ON TB1004(ID) WITH(ONLINE=OFF,MAXDOP=1) GO --======================== --刪除索引 DROP INDEX IDX_OBJECTID ON TB1004 --======================== --聯機創建索引 --耗時53秒 CREATE INDEX IDX_OBJECTID ON TB1004(ID) WITH(ONLINE=ON,MAXDOP=1) GO --======================== --刪除索引 DROP INDEX IDX_OBJECTID ON TB1004
擴展會話捕獲到的數據:
使用SELECT OBJECT_NAME(1269579561)查看發現OBJECT對象為TB1004
由上面的數據我們不難發現這么幾個結論:
1.無論聯機還是脫機創建索引時,架構修改鎖的對象為HOBT和METADATA
2.刪除索引操作時,架構修改鎖的對象為OBJECT:TB1004
3.聯機索引創建耗時53秒,脫機索引創建耗時53秒(在沒有外部數據操作情況下),脫機索引創建耗時遠小於聯機索引創建
--============================================================
是時候揭曉謎底啦
我們開啟一個會話,執行下面SQL:
--使用NOLOCK訪問表 SELECT * FROM TB1004 WITH(NOLOCK)
再另外開啟一個會話,執行下面SQL:
--==================================================== --使用SP_LOCK來獲取鎖 --查找某個對象上的鎖 DECLARE @T TABLE ( SPID BIGINT, DataBaseID INT, OBJECTID BIGINT, IndexID BIGINT, LockType VARCHAR(20), LockResource NVARCHAR(200), LockMode NVARCHAR(20), LockStats NVARCHAR(200) ) INSERT INTO @T EXEC SP_LOCK SELECT SPID ,DataBaseID ,DB_NAME(DataBaseID) AS DataBaseName ,OBJECTID ,OBJECT_Name(OBJECTID,DataBaseID) ObjectName ,IndexID ,LockType ,LockResource ,LockMode ,LockStats FROM @T WHERE OBJECTID=OBJECT_ID('TB1004')
我們發現,即使使用NOLOCK提示,仍需要SCH_S鎖,這就是為什么DROP INDEX時被阻塞的原因,因為在同一個資源(object_ID:1269579561)上有互斥的SCH_M鎖和SCH_S鎖。
而對於聯機索引創建,索引創建會話使用的SCH_M鎖的對象與NOLOCK查詢的使用的SCH_S鎖的對象不是同一個,因此不會阻塞。
相信諸位看官到此應該深深地明白WHY了吧。
--=====================================================================
再次感謝群友”一川晴雨“,妹子為你而上