為什麼 SQL Server 不使用它推薦的索引?
查詢
我有一個要優化的特定查詢:
SELECT * /*12 columns*/ FROM [dbo].[EnterpriseGroup] WHERE (EnterpriseGroup.ChildId = 123 OR EnterpriseGroup.FatherId = 234) AND StatusCd >= 2
也已經有一個索引
FatherId
,但沒有ChildId
。主鍵在 12 個選定的列中,但這裡的 WHERE 子句中沒有使用這些列。使用
這是一個簡單的查詢,但它在日常工作中非常非常頻繁地執行。該表也很小,大約 8000 行。
該查詢用於查找企業組。大約有 200 萬個企業條目,因此只有不到 0.5% 的企業條目有匹配的組行,因此大多數時候不會找到任何組。
建議
當使用 SSMS 並檢查“實際執行計劃”時,它給出了這個計劃:
顯示的謂詞實際上是 WHERE 子句。
並且,它建議創建一個索引,它基本上是在 WHERE 子句上建立索引,並將所有查詢的列直接添加到索引中。對我來說似乎不是很聰明,但也許這就是這個問題的全部內容:
/* Missing Index Details from SQLQuery6.sql ..... The Query Processor estimates that implementing the following index could improve the query cost by 68.9052%. */ /* USE [...] GO CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>] ON [dbo].[EnterpriseGroup] ([StatusCd]) INCLUDE (....all the 12 queried columns......) GO */
結果
創建推薦索引後,我得到以下計劃:
根本沒用!(並且 Table Scan 懸停資訊完全相同,仍然有 WHERE 子句的副本作為謂詞)
問題
為什麼 SQL Server 不使用 SSMS 建議創建的現有索引?
如果不是,那麼在 SSMS(SQL Server Management Studio)針對 Microsoft SQL Server 的執行計劃中缺少索引建議的意義何在?
筆記
注意:我不是 DBA,而是軟體開發人員。我對此進行了一些閱讀,包括:https ://www.brentozar.com/archive/2013/07/dude-who-stole-my-missing-index-recommendation/但我沒有向我澄清。
注意:如果重要: - SQL Server 版本 11.0.7493.4,在 Windows NT 6.3 上執行。- Microsoft SQL Server Management Studio 版本為 11.0.7493.4
假設您的
WHERE
條件仍然使用ChildId
,FatherId
和StatusCd
。從統計的角度來看,這可能是ChildId
或者FatherId
更具選擇性。取出ChildId
和FatherId
取出WHERE
子句應該會導致使用該新索引StatusCd
,因為indexed column
.index
如果您將滑鼠懸停在 Table Scan 部分上,您應該會看到如下圖所示的內容:
即使它建議創建該索引,它也有可能仍在通過
ChildId
或進行查詢FatherId
。
ChildId
如果或FatherId
更具選擇性,它會這樣做。假設StatusCd >= 2
返回 8,000 行中的 6,000 行。但要麼ChildId = 123
或FatherId = 234
只有 1 行。然後對該列進行表掃描,並在事後應用其餘條件是一個更有效的查詢計劃(從理論上講),而不是返回所有 6,000 行StatusCd >= 2
並嘗試應用ChildId
orFatherId
條件。這是我從不久前提出的一個問題中學到的。回答它的人有一個很好的方式來解釋我在這裡想說的話。 查詢優化器是否更喜歡在列之前查詢常量?
希望這會有所幫助。
有許多未知變數需要我們正確回答,例如表大小、列的統計資訊和現有索引。
首先,您的表是一個堆(而不是聚集索引),如果它經常更新,它可能會被轉髮指針碎片化。這可能會對引擎必須執行的讀取次數產生負面影響。
其次,在實際的查詢計劃中,是否可以將滑鼠懸停在表掃描運算符上?它將向我們展示謂詞 - 可能正在進行簡單或強制的參數化,它不會使用統計數據來衡量您的價值。
三、指標是什麼?您需要選擇所有列(選擇星號)嗎?你有關於childId或fatherId的索引嗎?
我推薦的索引(不知道統計資訊將是 1. childId,StatusCd 包括(您的 select 子句中的所有列) 2. FatherId,StatusCd 包括(您的 select 子句中的所有列)
然後我會重寫查詢如下:
SELECT [columns] FROM [dbo].[EnterpriseGroup] WHERE EnterpriseGroup.ChildId = 123 AND StatusCd >= 2 UNION ALL SELECT [columns] FROM [dbo].[EnterpriseGroup] WHERE EnterpriseGroup.FatherId = 234 AND StatusCd >= 2
您將在同一個表中搜尋兩次,一次使用 Child 上的索引,第二次使用 Father 上的索引,然後連接結果。