INDEX--創建索引和刪除索引時的SCH_M鎖


最近有一個困惑,生產服務器上有一表索引建得亂七八糟,經過整理后需要新建幾個索引,再刪除幾個索引,建立索引時使用聯機(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了吧。

--=====================================================================

再次感謝群友”一川晴雨“,妹子為你而上


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM