記憶體中的參數嗅探和多個計劃
我們有一個多租戶數據庫。FirmID 是分區鍵,我們有很多不同的公司。
我遇到了一個參數嗅探問題,我花了很多時間來解決它。
我寧願不使用任何
$$ Options $$關於查詢。 我最近的想法是更改我用於公司的參數的名稱。在下面的片段中,您將看到我沒有使用@FirmID,而是將其稱為@Firm611,其中611 是ID 的實際公司。這將為我提供每家公司的獨特查詢。
select c.ID [_cid], c.Name [Name] from vwClaims c with(nolock) where c.FirmID=@Firm611 and (c.Name is not null and c.Name!='') select c.ID [_cid], c.Name [Name] from vwClaims c with(nolock) where c.FirmID=@Firm625 and (c.Name is not null and c.Name!='')
執行 Brent Ozar 的 sp_BlitzCache 後,我發現它只是編譯為相同的查詢並導致重複的記憶體條目:
我的問題是我讀的結果對嗎?即使我更改了參數名稱,它真的仍然使用相同的計劃和參數嗅探嗎?
這裡有幾個不同的問題。
問:如果兩個相同的查詢使用兩個不同的參數名稱,SQL Server 會做什麼?
SQL Server 將為每個查詢建構一個單獨的執行計劃。SQL Server 將即使是最輕微的文本更改(甚至像空格一樣簡單)都視為不同的查詢。
每個變體都將完全獨立於另一個變體進行編譯和記憶體。
問:這兩個查詢會得到不同的計劃還是相同的計劃?
SQL Server 將檢查參數的內容並基於此建構執行計劃。@Firm611 的計劃將根據@Firm611 的選擇性編制,@Firm625 的計劃將根據@Firm625 的選擇性編制。
如果這兩個參數碰巧具有相同的選擇性,則兩個編譯計劃最終可能具有完全相同的形狀、相同的索引使用、相同的記憶體授予等。但是,它們仍將被獨立編譯並分別記憶體。
問:sp_BlitzCache 中的警告是什麼意思?
警告表明您有兩個“不同”的查詢,它們並沒有真正的不同——它們得到的計劃形狀相同。這意味著您正在浪費時間建構它們的多個版本,因為:
- 您正在浪費 CPU 資源重複編譯相同的計劃
- 您正在浪費記憶體記憶體不同的計劃(實際上是相同的計劃)
問:OPTION (RECOMPILE) 會解決這個問題嗎?
不,因為那樣它們每次執行時都會重新編譯,從而導致更高的 CPU 使用率。我只建議在查詢每分鐘執行一次或更少時重新編譯提示 - 如果它執行的頻率高於此,我開始擔心 CPU 成本,因為您使用該技巧的所有查詢的頻繁編譯。
我使用每分鐘一次的基線,因為這只是這個查詢 - 您必須記住伺服器上的總工作量。如果您在每個查詢上添加重新編譯提示,您最終可能會獲得 100% 的 CPU 使用率 - 即使每個單獨的查詢每秒僅執行 1 次,當您考慮每個查詢時,這也是很多編譯。
我發現它只是編譯為相同的查詢並導致重複的記憶體條目
我可以明白為什麼你會以這種方式讀取輸出(DistinctPlanCount),但似乎程式碼有一個錯誤。
COUNT
聚合不應該引用query_plan_hash
(這query_hash
是一個常數)。FROM ( SELECT query_hash, COUNT(DISTINCT(query_hash)) AS DistinctPlanCount, COUNT(query_hash) AS PlanCount FROM sys.dm_exec_query_stats GROUP BY query_hash ) AS q
應該:
FROM ( SELECT qs.query_hash, COUNT(DISTINCT(qs.query_plan_hash)) AS DistinctPlanCount, COUNT(qs.query_plan_hash) AS PlanCount FROM sys.dm_exec_query_stats AS qs GROUP BY qs.query_hash ) AS q
您正在使用該過程的相當舊版本,但問題仍然存在於最新版本中(儘管已應用其他修復程序)。
即使我更改了參數名稱,它真的仍然使用相同的計劃和參數嗅探嗎?
不,您將按預期獲得每個參數名稱的單獨計劃。每個計劃(每個公司)在第一次執行時嗅探自己的參數,並在後續呼叫中重用該計劃,直到由於任何原因發生重新編譯。
換句話說,您的計劃正在按您希望的那樣工作。當第一次執行時的 per-firm 參數值沒有為同一公司的所有後續執行生成合理的計劃時,您仍然會遇到問題。這只是普通的參數敏感計劃問題,具有通常的解決方法。
正如大衛布朗所說:
這是將所有租戶放在同一個數據庫中的大問題之一。您應該計劃至少將“大”和“小”租戶分開。此外,在查詢中簡單地對tenantId 進行硬編碼以獲得特定於租戶的計劃也是完全合適的。參數用於您想要共享計劃時。