索引搜尋與索引掃描
查看一個執行緩慢的查詢的執行計劃,我注意到一些節點是索引搜尋,其中一些是索引掃描。
索引查找和索引掃描有什麼區別?
哪個表現更好?
SQL 如何選擇一個而不是另一個?
我意識到這是 3 個問題,但我認為回答第一個問題將解釋其他問題。
簡短版:seek 更好
不那麼短的版本:seek 通常要好得多,但是大量的 seek(例如,由於糟糕的相關子查詢的不良查詢設計,或者因為您在游標操作或其他循環中進行了許多查詢)可能比掃描,特別是如果您的查詢最終可能會從受影響的表中的大多數行返回數據。
它有助於覆蓋整個家庭的數據查找操作,以充分了解性能影響。
**表掃描:**完全沒有與您的查詢相關的索引,規劃器被迫使用表掃描,這意味著查看每一行。這可能導致從磁碟讀取與表數據相關的每個頁面,這通常是最壞的情況。請注意,對於某些查詢,即使存在有用的索引,它也會使用表掃描 - 這通常是因為表中的數據非常小,以至於遍歷索引更加麻煩(如果是這種情況,您會期望計劃隨著數據的增長而改變,假設指數的選擇性衡量是好的)。
**使用行查找的索引掃描:**如果沒有找到可直接用於查找的索引,但存在包含正確列的索引,則可以使用索引掃描。例如,如果您有一個包含 20 列的大表,並且在 column1,col2,col3 上有一個索引並且您發出
SELECT col4 FROM exampletable WHERE col2=616
,在這種情況下,掃描索引以進行查詢col2
比掃描整個表要好。一旦找到匹配的行,則需要讀取數據頁以獲取 col4 以進行輸出(或進一步連接),這就是您在查詢計劃中看到它時的“書籤查找”階段。**沒有行查找的索引掃描:**如果上面的範例是,
SELECT col1, col2, col3 FROM exampletable WHERE col2=616
那麼不需要額外的努力來讀取數據頁:一旦col2=616
找到匹配的索引行,所有請求的數據都是已知的。這就是為什麼您有時會看到永遠不會搜尋的列,但可能會被請求輸出,添加到索引的末尾 - 它可以節省行查找。當出於這個原因並且僅出於這個原因將列添加到索引時,將它們與INCLUDE
子句一起添加以告訴引擎它不需要優化索引佈局以基於這些列進行查詢(這可以加快對這些列的更新) . 索引掃描也可能來自沒有過濾子句的查詢:SELECT col2 FROM exampletable
將掃描此範例索引而不是表頁面。索引查找(有或沒有行查找)**:**在查找中,並非所有索引都被考慮。對於查詢
SELECT * FROM exampletable WHERE c1 BETWEEN 1234 AND 4567
,查詢引擎可以通過對索引進行基於樹的搜尋來找到將匹配的第一行,c1
然後它可以按順序導航索引,直到它到達範圍的末尾(這與查詢相同因為c1=1234
即使對於操作,也可能有許多行與條件匹配=
)。這意味著只需要讀取相關的索引頁(加上初始搜尋所需的一些),而不是索引(或表)中的每一頁。**聚集索引:**使用聚集索引,表數據儲存在該索引的葉節點中,而不是儲存在單獨的堆結構中。這意味著無論需要什麼列,在使用該索引查找行之後都不需要任何額外的行查找[除非您有離頁數據,如
TEXT
列或VARCHAR(MAX)
包含長數據的列]。由於這個原因,您只能擁有一個聚集索引$$ 1 $$,聚集索引是你的表,而不是有一個單獨的堆結構,所以如果你使用一個$$ 2 $$仔細選擇放置位置以獲得最大收益。
還要注意聚群索引因為“聚群鍵”為表而包含在表上的每一個非聚群索引中,所以寬聚群索引一般不是一個好主意。
$$ 1 $$實際上,您可以通過定義覆蓋或包含表上**每一列的非聚集索引來有效地擁有多個聚集索引,但這可能會浪費空間並影響寫入性能,因此如果您考慮這樣做,請確保您確實需要到。
$$ 2 $$當我說“如果您使用聚集索引”時,請注意通常建議您在每個表上都有一個。與所有經驗法則一樣,也有例外,除了批量插入和無序讀取(可能是 ETL 程序的暫存表)之外幾乎看不到什麼的表是最常見的反例。
附加點:不完整的掃描:
重要的是要記住,根據查詢的其餘部分,表/索引掃描實際上可能不會掃描整個表——如果邏輯允許,查詢計劃可能會導致它提前中止。最簡單的例子是
SELECT TOP(1) * FROM HugeTable
- 如果您查看查詢計劃,您會看到掃描只返回了一行,如果您查看 IO 統計資訊 (SET STATISTICS IO ON; SELECT TOP(1) * FROM HugeTable
),您會發現它只讀取了一個非常小的數字頁數(可能只有一個)。
WHERE
如果or子句的謂詞JOIN ... ON
可以與作為數據源的掃描同時執行,也會發生同樣的情況。查詢計劃者/執行者有時可以非常聰明地將謂詞推回數據源以允許以這種方式提前終止掃描(有時您可以巧妙地重新安排查詢以幫助它這樣做!)。雖然數據按照標準查詢計劃顯示中的箭頭從右到左流動,但邏輯從左到右執行,並且每個步驟(從右到左)不一定在下一個可以開始之前執行完成。在上面的簡單範例中,如果您將查詢計劃中的每個塊視為代理,代理會SELECT
向代理詢問TOP
一行,而後者又會詢問TABLE SCAN
一個代理,然後SELECT
代理要求另一個,但TOP
代理知道沒有必要甚至不費心詢問表格閱讀器,SELECT
代理得到“不再相關”的響應並且知道所有工作都已完成。當然,許多操作會阻止這種優化,在更複雜的範例中,表/索引掃描確實會讀取每一行,但請注意不要得出任何掃描都必須是昂貴操作的結論。