本文說明如何針對編譯鎖定所造成的封鎖問題進行疑難解答和解決。
原始產品版本:SQL Server
原始 KB 編號: 263889
摘要
Microsoft SQL Server 中,一般只會有一份預存程式計劃複本一次在快取中。 強制執行此作業需要編譯程式的某些部分進行串行化,而且此同步處理部分是使用編譯鎖定來完成的。 如果許多連接同時執行相同的預存程式,而且每次執行時都必須取得該預存程式的編譯鎖定,會話標識碼 (SPID) 可能會開始彼此封鎖,因為它們每次嘗試取得對象的獨佔編譯鎖定。
以下是可在封鎖輸出中觀察到之編譯封鎖的一些典型特性:
waittype針對已封鎖的 和 (通常) 封鎖會話 SPID 為 (獨佔),且waitresource格式OBJECT: dbid: object_id [[COMPILE]]為LCK_M_X,其中object_id是預存程式的物件標識符。封鎖程式具有
waittypeNULL,狀態為可執行。 已封鎖的會話有waittypeLCK_M_X(獨佔鎖定),狀態為睡眠狀態。雖然封鎖事件的整體持續時間可能很長,但是沒有單一會話 (SPID) 會長時間封鎖其他SPID。 有滾動封鎖;一旦一個編譯完成,另一個SPID就會接管前端封鎖程式的角色數秒或更少,依此推移。
下列資訊來自這種封鎖期間的 快照 sys.dm_exec_requests 集:
session_id blocking_session_id wait_type wait_time waitresource
---------- ------------------- --------- --------- ----------------------------
221 29 LCK_M_X 2141 OBJECT: 6:834102 [[COMPILE]]
228 29 LCK_M_X 2235 OBJECT: 6:834102 [[COMPILE]]
29 214 LCK_M_X 3937 OBJECT: 6:834102 [[COMPILE]]
13 214 LCK_M_X 1094 OBJECT: 6:834102 [[COMPILE]]
68 214 LCK_M_X 1968 OBJECT: 6:834102 [[COMPILE]]
214 0 LCK_M_X 0 OBJECT: 6:834102 [[COMPILE]]
在數據 waitresource 行 (6:834102), 6 是資料庫標識碼,834102是對象標識碼。 此物件識別碼屬於預存程式,而非數據表。
導致編譯鎖定的案例
下列案例描述預存程式或觸發程式上獨佔編譯鎖定的原因。
預存程式在沒有完整名稱的情況下執行
- 執行預存程式的使用者不是程序的擁有者。
- 預存程式名稱未使用物件擁有者的名稱完整。
例如,如果使用者 dbo 擁有物件 dbo.mystoredproc 和另一位使用者, Harry則使用 命令 exec mystoredproc執行這個預存程式,則物件名稱的初始快取查閱會失敗,因為物件不是擁有者限定。 (目前還不清楚是否有另 Harry.mystoredproc 一個名為的預存程式存在。因此,SQL Server 無法確定 的快取計劃是正確的執行計劃 dbo.mystoredproc 。然後,SQL Server 會取得程式的獨占編譯鎖定,並準備編譯程式。 這包括將物件名稱解析為物件標識碼。 在 SQL Server 編譯計劃之前,SQL Server 會使用此物件標識符來執行更精確的程式快取搜尋,而且即使沒有擁有者資格,也可以找出先前編譯的計劃。
如果找到現有的計劃,SQL Server 會重複使用快取的計劃,而且實際上不會編譯預存程式。 不過,缺少擁有者資格會強制 SQL Server 執行第二個快取查閱,並在程式判斷現有的快取執行計劃可重複使用之前取得獨占編譯鎖定。 取得鎖定並執行查閱,以及達到此點所需的其他工作,可能會造成導致封鎖的編譯鎖定延遲。 如果許多不是預存程式擁有者的使用者,請同時執行程式,而不需提供擁有者的名稱,則尤其如此。 即使您沒有看到 SPID 等待編譯鎖定,缺乏擁有者資格可能會導致預存程式執行延遲,並導致高 CPU 使用率。
發生此問題時,SQL Server 擴充事件會話中會記錄下列事件序列。
| 事件名稱 | Text |
|---|---|
| rpc_starting | mystoredproc |
| sp_cache_miss | mystoredproc |
| sql_batch_starting | mystoredproc |
| sp_cache_hit | mystoredproc |
| ... | ... |
sp_cache_miss 發生於依名稱排序的快取查閱失敗時,但在將模棱兩可的物件名稱解析為對象標識碼且有 sp_cache_hit 事件之後,最終會在快取中找到相符的快取計劃。
這個編譯鎖定問題的解決方案是確保預存程序的參考具有擁有者資格。 (而不是 exec mystoredproc,請使用 exec dbo.mystoredproc。)雖然擁有者資格對於效能考慮很重要,但您不需要使用資料庫名稱來限定預存程式,以避免額外的快取查閱。
使用標準封鎖疑難解答方法,即可偵測編譯鎖定所造成的封鎖。
預存程式經常重新編譯
重新編譯是預存程式或觸發程式上編譯鎖定的其中一個說明。 導致預存程式重新編譯的方法包括 EXECUTE... WITH RECOMPILE、 CREATE PROCEDURE ...WITH RECOMPILE或使用 sp_recompile。 如需詳細資訊,請參閱重新編譯預存程序。 在此情況下,解決方案是減少或消除重新編譯。
預存程式前面加上 sp_**
如果您的預存程式名稱以前置詞開頭 sp_ ,而且它不在 master 資料庫中,即使擁有者符合預存程式資格,還是會在每次執行快取叫用之前看到 sp_cache_miss 。 這是因為前置 sp_ 詞會告訴 SQL Server 預存程式是系統預存程式,而系統預存程式有不同的名稱解析規則。 (慣用的位置位於 master 資料庫中。使用者建立預存程式的名稱不應該以 sp_開頭。
預存程式會使用不同的大小寫來叫用 (大 /下)
如果使用與用來建立它之案例不同的字母大小寫(大寫或小寫)來執行擁有者限定程式,則程式可以觸發 CacheMiss 事件或要求 COMPILE 鎖定。 為了說明,請注意 與中使用的CREATE PROCEDURE dbo.SalesData ...EXEC dbo.salesdata不同字母大小寫。 最後,程式會使用快取的計劃,而且不會重新編譯。 但是,編譯鎖定的要求有時會造成 稍早所述的封鎖鏈結 狀況。 如果有許多會話 (SPID) 嘗試使用與用來建立它的情況不同的案例來執行相同的程式,就會發生封鎖鏈結。 不論伺服器或資料庫上使用的排序順序或定序為何,都是如此。 此行為的原因是,用來在快取中尋找程序的演算法是以哈希值為基礎(基於效能),如果案例不同,哈希值可能會變更。
解決方案是使用與應用程式執行程式時所使用的字母大小寫相同的字母大小寫來卸除和建立程式。 您也可以使用正確的大小寫(大或小寫),確定程式是從所有應用程式執行。
預存程式會叫用為 Language 事件
如果您嘗試將預存程式當做 Language Event 而不是 RPC 來執行,SQL Server 必須剖析和編譯語言事件查詢、判斷查詢正嘗試執行特定程式,然後嘗試在該程式的快取中尋找計劃。 若要避免 SQL Server 必須剖析和編譯語言事件的情況,請確定查詢會以 RPC 的形式傳送至 SQL Server。 例如,在 .NET 程式代碼中,您可以使用 SqlCommand.CommandType.StoredProcedure 來確保 RPC 事件。
預存程式或sp_executesql使用大於 8 KB 的字串參數
如果您呼叫預存程式或 sp_executesql 並傳遞大於 8 KB 的字串參數,SQL Server 會使用二進位大型物件 (BLOB) 數據類型來儲存參數。 因此,此執行的查詢計劃不會保存在計劃快取中。 因此,每次執行預存程式或 sp_executesql 必須取得編譯鎖定,才能編譯新的計劃。 此計劃會在執行完成時捨棄。 如需詳細資訊,請參閱執行計劃快取和重複使用中大於 8 KB 的字串常值中的附註。 若要避免此案例中的編譯鎖定,請將 參數的大小縮減為小於 8 KB。