書籤能夠幫助SQL Server快速從非彙集索引條目導向到對應的行,其實這東西幾句話我就能說明白。數據庫
若是表有彙集索引(區段結構),那麼書籤就是從非彙集索引找到彙集索引後,利用匯集索引定位到數據。此處的書籤就是彙集索引。若是表沒有彙集索引(堆結構)。那麼掃描非彙集索引後,經過RID定位到數據,那麼此處書籤就是RID。ui
所謂的書籤查找,就是經過彙集索引,而後利用匯集索引或RID定位到數據。spa
不論表示堆結構仍是區段結構,數據的存放都是數據庫文件的某文件->某頁->某行,所以定位數據的文件組合起來就是
文件號:頁號:行號。這三個數字就是RID。如文件1的第77頁的第12行的RID就是1:77:12。指針
堆結構與區段結構不一樣,一般堆上的行不會改變位置,一旦他們被插入某個頁中,他們就會一直在那個位置。在堆上的行不多移動,若是行被移動的話,他們會在原來的位置留下指向其移動到的新位置的指針。而區段結構的行,是能夠移動的,在添加數據或整理索引時,均可以會被移動位置。code
由於在堆上的行不多移動,因此RID就能夠惟一標識某一行,RID的值不只僅不變,RID所表示的行的物理位置也不會變,這使得RID的值更適宜做爲書籤。這也是爲何SQL Server在堆上創建的非彙集索引的書籤都使用RID。blog
一、堆上的非彙集索引:基於RID的書籤索引
CREATE NONCLUSTERED INDEX FK_ProductID_ModifiedDate --主鍵不是彙集索引,沒有彙集索引 ON Sales.SalesOrderDetail(ProductID, ModifiedDate) INCLUDE (OrderQty, UnitPrice, LineTotal)
部分數據順序:內存
注意到以上數據是無序的。it
上面創建的非彙集索引由於使用了RID做爲書籤,直接指向對應行所在的物理位置,所以效率不錯。雖然RID值用於鍵查找很是高效,但書籤中包含的值與具體的用戶數據無關。ast
二、在彙集索引上的非彙集索引:基於彙集鍵的書籤
若是表示基於彙集索引的,則表內數據能夠在表移動。所以,對於彙集索引來講,RID並不能一直不變的定位一個相同的行。所以必須用另外的方法定位行,這個方法就是使用匯集索引的索引鍵。
使用匯集索引鍵做爲書籤可使得當數據在頁中的行改變時,不須要非彙集索引的書籤的值進行變更,所以非彙集索引的鍵就能夠用於去找底層表的數據,即根據書籤取數據再也不基於物理位置,而是基於彙集索引查找。
以彙集索引鍵做爲非彙集索引的書籤最好要彙集索引鍵知足以下標準:
索引應該具備惟一性:每個索引條目書籤都應該使得書籤能夠經過彙集索引的鍵值惟一的確認表中的一行,若是你建立的彙集索引鍵值不惟一,SQL Server將會爲有重複鍵值的每一行自動加上一個叫uniquifier的東西使得每一行惟一。這個uniquifier對客戶端是透明的。對因而否能夠容許彙集索引鍵重複,要考慮如下兩點:
索引鍵應該短:索引鍵所佔的字節數應該短.由於這個鍵還會佔用非彙集索引書籤的空間。好比Contact表中以Last name / first name / middle name / street組合做爲索引鍵看上去不錯,但若是表中存在多個非彙集索引的話狀況就有些微妙了。n個非彙集索引使得Last name / first name / middle name / street這些字段被存儲在n+1個位置。
索引鍵最好不要變更:也就是索引鍵的值最好不要變更。對於彙集索引鍵的修改會使得基於這個彙集索引的全部非彙集索引一樣進行修改。因此對於彙集索引的一次update會形成n個非彙集索引書籤的update+1個彙集索引鍵值自己的update。
下面以一個示例來幫助理解書籤查找:
假設數據庫有一張表以下:
咱們再Name列建一個非彙集索引,而後執行下面的語句:
從執行計劃咱們能夠看到,由於Age列並不在非彙集索引中,因此SQL Server經過「鍵查找」引導到彙集表獲取數據,這就是書籤查找。
書籤查找的目的,就是爲了從非彙集索引導航到基本表獲取非彙集索引中並未包含的信息。
書籤查找要求訪問索引頁面以外的數據頁面,訪問兩組頁面增長了查詢邏輯讀操做次數。並且,若是頁面不在內存中,書籤查找可能須要在磁盤上一個隨機I/O操做來從索引頁面跳轉到數據頁面,還須要必要的CPU能力來聚集這一數據並執行必要的操做。這是由於對於大的表,索引頁面和對應的數據頁面一般在磁盤上並不臨近。
若是須要增長邏輯讀操做或者開銷較大的物理讀操做使書籤查找的數據檢索操做開銷至關大,這個開銷因素是非彙集索引更適合於返回較小的數據行數的緣由。隨着查詢檢索的行數增長,書籤查找的開銷將變得沒法接受。
爲了理解書籤查找隨着檢索行數增長而使feu彙集索引無效,下面來看一個實例:
仍是那張Person表,一萬數據。此次,我把索引建在Id列,Id列的惟一性是1,由於原來Id列是作主鍵+彙集索引的,但被我刪掉了。
咱們來看看下面兩個查詢的執行計劃,
返回100條:
返回300條:
咱們看到,當要求返回300條數據的時候,SQL Server就不在使用Id列上的非彙集索引,而是直接進行表掃描了。由於SQL Server認爲執行300次書籤查找還不如直接對一張1萬條記錄的表進行全表掃描。
由上面的實例能夠得出結論,返回大的結果集將增長書籤查找的開銷,甚至低於表掃描。所以在返回較大結果集的狀況下,必須考慮避免書籤查找的可能性。
書籤查找多是一個開銷較大的操做,因此應該分析查詢計劃,在執行計劃中選擇一個關鍵字查找步驟的緣由。可能發現能夠經過在非彙集索引鍵中包含丟失的行,或者做爲索引頁面級別上的包含列來避免書籤查找,從而避免與書籤查找相關的開銷。
從上面的實例,咱們能夠提出觀點:若是查詢的各部分(不僅是選擇列表)中引用的列不都包含在使用的非彙集索引中,就會發生書籤查找操做。
下面介紹一個技巧,咱們點擊某一個執行計劃的圖標以後,就能在右側的屬性信息欄裏獲取到相關的執行信息。例如,輸出列表就是本執行計劃的要返回的列。
由於書籤查找的相對開銷可能很是高,因此應該儘量嘗試擺脫書籤查找操做。下面給出一下方案。
一、使用匯集索引
對於彙集索引,索引的葉子頁面和表的數據頁面相同。所以,當讀取彙集索引鍵列的值時,數據引擎能夠讀取其餘列的值而不須要任何導航。例如前面的區間數據查詢的操做,SQL Server經過B樹結構進行查找是很是快速的。
把非彙集索引轉換爲一個彙集索引提及來很簡單。可是,這個例子和大部分可能遇到的狀況下,這不可能作到,由於表已經有了一個彙集索引。這個表的彙集索引剛好是主鍵。必須卸載掉全部的外鍵約束,卸載而且重建爲一個非彙集索引。這不只要考慮所涉及的工做,還可能嚴重地影響依賴於現有彙集索引的其餘查詢。
二、使用覆蓋索引
爲了理解覆蓋索引是如何避免書籤查找,咱們仍是對於Person來執行以下兩個查詢:
下面修改索引增長Name列。
因爲非彙集索引上已經有了須要查詢的Id和Name列的數據,因此不在須要書籤查找定位到基本表。
三、使用索引鏈接
若是覆蓋索引變得很是寬,那麼可能要考慮索引鏈接技術。索引鏈接技術使用兩個或更多索引之間的一個索引交叉來徹底覆蓋一個查詢。由於索引鏈接技術須要訪問多餘一個索引,它必須在全部索引鏈接中使用的索引上執行邏輯讀。所以,索引鏈接須要比覆蓋索引更高的邏輯讀數量。可是,由於索引鏈接所用的多個窄索引可以比寬的覆蓋索引服務更多的查詢。因此索引鏈接也能夠做爲避免書籤查找的一種技術來考慮。
咱們來看下面的實例:
留意到,上面的例子咱們建立了兩個非彙集索引,一個在 Id列,一個在Name列。可是咱們的查詢須要同時返回Id列和Name列。而這兩個非彙集索引都不徹底包含要返回列。這個時候,哈希匹配目的就是經過定位到索引,而不用定位到基本表就可以得到咱們所須要的所有數據,這樣索引鏈接就避免了書籤查找。