1. 程式人生 > >轉發SQLSERVER資料庫索引實現

轉發SQLSERVER資料庫索引實現

中小企業MIS系統的管理基本上由兩大部份組成,一是前臺的視覺化操作,二是後臺的資料庫管理。網管對前臺的管理和維護工作包括保障網路鏈路通暢、處理MIS終端的突發事件以及對操作員的管理、培訓等,這是網管們日常做得最多、最辛苦的功課;然而MIS系統架構中同等重要的針對資料庫的管理、維護和優化工作,現實中似乎並沒有得到網管朋友的足夠重視,看起來這都是程式設計師的事,事實上,一個網管如果能在MIS設計期間就資料表的規範化、表索引優化、容量設計、事務處理等諸多方面與程式設計師進行卓有成效的溝通和協作,那麼日常的前臺管理工作將會變得大為輕鬆,因為在某種意義上,資料庫管理系統就相當於作業系統,在系統中佔有同樣重要的位置。

這正是SQL SERVER等資料庫管理系統和dBASEX、ACCESS等資料庫檔案系統的本質區別,所以,對資料庫管理系統操作能力的強弱在某種程度上也折射出了網管的水平——個人認為,稱得上優秀的Admin,至少應該是一個稱職的DBA(資料庫管理員)。

下面以SQL SERVER(下稱 SQLS)為例,將資料庫管理中難於理解的“索引原理”問題給各位朋友作一個深入淺出的介紹。其他的資料庫管理系統如Oracle、Sybase等,朋友們可以融會貫通,舉一反三。

一、資料表的基本結構

建立資料庫的目的是管理大量資料,而建立索引的目的就是提高資料檢索效率,改善資料庫工作效能,提高資料訪問速度。對於索引,我們要知其然,更要知其所以然,關鍵在於認識索引的工作原理,才能更好的管理索引。

為認識索引工作原理,首先有必要對資料表的基本結構作一次全面的複習。

SQLS當一個新表被建立之時,系統將在磁碟中分配一段以8K為單位的連續空間,當欄位的值從記憶體寫入磁碟時,就在這一既定空間隨機儲存,當一個8K用完的時候,SQLS指標會自動分配一個8K的空間。這裡,每個8K空間被稱為一個數據頁(Page),又名頁面或資料頁面,並分配從0-7的頁號,每個檔案的第0頁記錄引導資訊,叫檔案頭(File header);每8個數據頁(64K)的組合形成擴充套件區(Extent),稱為擴充套件。全部資料頁的組合形成堆(Heap)。

SQLS規定行不能跨越資料頁,所以,每行記錄的最大資料量只能為8K。這就是char和varchar這兩種字串型別容量要限制在8K以內的原因,儲存超過8K的資料應使用text型別,實際上,text型別的欄位值不能直接錄入和儲存,它只是儲存一個指標,指向由若干8K的文字資料頁所組成的擴充套件區,真正的資料正是放在這些資料頁中。

頁面有空間頁面和資料頁面之分。

當一個擴充套件區的8個數據頁中既包含了空間頁面又包括了資料或索引頁面時,稱為混合擴充套件(Mixed Extent),每張表都以混合擴充套件開始;反之,稱為一致擴充套件(Uniform Extent),專門儲存資料及索引資訊。

表被建立之時,SQLS在混合擴充套件中為其分配至少一個數據頁面,隨著資料量的增長,SQLS可即時在混合擴充套件中分配出7個頁面,當資料超過8個頁面時,則從一致擴充套件中分配資料頁面。

空間頁面專門負責資料空間的分配和管理,包括:PFS頁面(Page free space):記錄一個頁面是否已分配、位於混合擴充套件還是一致擴充套件以及頁面上還有多少可用空間等資訊;GAM頁面(Global allocation map)和SGAM頁面(Secodary global allocation map):用來記錄空閒的擴充套件或含有空閒頁面的混合擴充套件的位置。SQLS綜合利用這三種類型的頁面檔案在必要時為資料表建立新空間;

資料頁或索引頁則專門儲存資料及索引資訊,SQLS使用4種類型的資料頁面來管理表或索引:它們是IAM頁、資料頁、文字/影象頁和索引頁。

在WINDOWS中,我們對檔案執行的每一步操作,在磁碟上的物理位置只有系統(system)才知道;SQL SERVER沿襲了這種工作方式,在插入資料的過程中,不但每個欄位值在資料頁面中的儲存位置是隨機的,而且每個資料頁面在“堆”中的排列位置也只有系統(system)才知道。

這是為什麼呢?眾所周知,OS之所以能管理DISK,是因為在系統啟動時首先載入了檔案分配表:FAT(File Allocation Table),正是由它管理檔案系統並記錄對檔案的一切操作,系統才得以正常執行;同理,作為管理系統級的SQL SERVER,也有這樣一張類似FAT的表存在,它就是索引分佈映像頁:IAM(Index Allocation Map)。

IAM的存在,使SQLS對資料表的物理管理有了可能。

IAM頁從混合擴充套件中分配,記錄了8個初始頁面的位置和該擴充套件區的位置,每個IAM頁面能管理512,000個數據頁面,如果資料量太大,SQLS也可以增加更多的IAM頁,可以位於檔案的任何位置。第一個IAM頁被稱為FirstIAM,其中記錄了以後的IAM頁的位置。

資料頁和文字/影象頁互反,前者儲存非文字/影象型別的資料,因為它們都不超過8K的容量,後者則只儲存超過8K容量的文字或影象型別資料。而索引頁顧名思義,儲存的是與索引結構相關的資料資訊。瞭解頁面的問題有助我們下一步準確理解SQLS維護索引的方式,如頁拆分、填充因子等。

二、索引的基本概念

索引是一種特殊型別的資料庫物件,它與表有著密切的聯絡。

索引是為檢索而存在的。如一些書籍的末尾就專門附有索引,指明瞭某個關鍵字在正文中的出現的頁碼位置,方便我們查詢,但大多數的書籍只有目錄,目錄不是索引,只是書中內容的排序,並不提供真正的檢索功能。可見建立索引要單獨佔用空間;索引也並不是必須要建立的,它們只是為更好、更快的檢索和定位關鍵字而存在。

再進一步說,我們要在圖書館中查閱圖書,該怎麼辦呢?圖書館的前臺有很多叫做索引卡片櫃的小櫃子,裡面分了若干的類別供我們檢索圖書,比如你可以用書名的筆畫順序或者拼音順序作為查詢的依據,你還可以從作者名的筆畫順序或拼音順序去查詢想要的圖書,反正有許多檢索方式,但有一點很明白,書庫中的書並沒有按照這些卡片櫃中的順序排列——雖然理論上可以這樣做,事實上,所有圖書的脊背上都人工的貼上了一個特定的編號①,它們是以這個順序在排列。索引卡片中並沒有指明這本書擺放在書庫中的第幾個書架的第幾本,僅僅指明瞭這個特定的編號。管理員則根據這一編號將請求的圖書返回到讀者手中。這是很形象的例子,以下的講解將會反覆用到它。

SQLS在安裝完成之後,安裝程式會自動建立master、model、tempdb等幾個特殊的系統資料庫,其中master是SQLS的主資料庫,用於儲存和管理其它系統資料庫、使用者資料庫以及SQLS的系統資訊,它在SQLS中的地位與WINDOWS下的登錄檔相當。

master中有一個名為sysindexes的系統表,專門管理索引。SQLS查詢資料表的操作都必須用到它,毫無疑義,它是本文主角之一。

檢視一張表的索引屬性,可以在查詢分析器中使用以下命令:select * from sysindexes where id=object_id(‘tablename’) ;而要查看錶的索引所佔空間的大小,可以使用系統儲存過程命令:sp_spaceused tablename,其中引數tablename為被索引的表名。

三、平衡樹

如果你通過書後的索引知道了一個關鍵字所在的頁碼,你有可能通過隨機的翻尋,最終到達正確的頁碼。但更科學更快捷的方法是:首先把書翻到大概二分之一的位置,如果要找的頁碼比該頁的頁碼小,就把書向前翻到四分之一處,否則,就把書向後翻到四分之三的地方,依此類推,把書頁續分成更小的部分,直至正確的頁碼。這叫“兩分法”,微軟在官方教程MOC裡另有一種說法:叫B樹(B-Tree,Balance Tree),即平衡樹。

一個表索引由若干頁面組成,這些頁面構成了一個樹形結構。B樹由“根”(root)開始,稱為根級節點,它通過指向另外兩個頁,把一個表的記錄從邏輯上分成兩個部分:“枝”—--非葉級節點(Non-Leaf Level);而非葉級節點又分別指向更小的部分:“葉”——葉級節點(Leaf Level)。根節點、非葉級節點和葉級節點都位於索引頁中,統稱為索引節點,屬於索引頁的範籌。這些“枝”、“葉”最終指向了具體的資料頁(Page)。在根級節點和葉級節點之間的葉又叫資料中間頁。

“根”(root)對應了sysindexes表的Root欄位,其中記載了非葉級節點的物理位置(即指標);非葉級節點位於根節點和葉節點之間,記載了指向葉級節點的指標;而葉級節點則最終指向資料頁。這就是“平衡樹”。

四、聚集索引和非聚集索引

從形式上而言,索引分為聚集索引(Clustered Indexes)和非聚集索引(NonClustered Indexes)。

聚集索引相當於書籍脊背上那個特定的編號。如果對一張表建立了聚集索引,其索引頁中就包含著建立索引的列的值(下稱索引鍵值),那麼表中的記錄將按照該索引鍵值進行排序。比如,我們如果在“姓名”這一欄位上建立了聚集索引,則表中的記錄將按照姓名進行排列;如果建立了聚集索引的列是數值型別的,那麼記錄將按照該鍵值的數值大小來進行排列。

非聚集索引用於指定資料的邏輯順序,也就是說,表中的資料並沒有按照索引鍵值指定的順序排列,而仍然按照插入記錄時的順序存放。其索引頁中包含著索引鍵值和它所指向該行記錄在資料頁中的物理位置,叫做行定位符(RID:Row ID)。好似書後面的的索引表,索引表中的順序與實際的頁碼順序也是不一致的。而且一本書也許有多個索引。比如主題索引和作者索引。

SQL Server在預設的情況下建立的索引是非聚集索引,由於非聚集索引不對錶中的資料進行重組,而只是儲存索引鍵值並用一個指標指向資料所在的頁面。一個表如果沒有聚集索引時,理論上可以建立249個非聚集索引。每個非聚集索引提供訪問資料的不同排序順序。

五、資料是怎樣被訪問的

若能真正理解了以上索引的基礎知識,那麼再回頭來看索引的工作原理就簡單和輕鬆多了。

(一)SQLS怎樣訪問沒有建立任何索引資料表:

Heap譯成漢語叫做“堆”,其本義暗含雜亂無章、無序的意思,前面提到資料值被寫進資料頁時,由於每一行記錄之間並沒地有特定的排列順序,所以行與行的順序就是隨機無序的,當然表中的資料頁也就是無序的了,而表中所有資料頁就形成了“堆”,可以說,一張沒有索引的資料表,就像一個只有書櫃而沒有索引卡片櫃的圖書館,書庫裡面塞滿了一堆亂七八糟的圖書。當讀者對管理員提交查詢請求後,管理員就一頭鑽進書庫,對照查詢內容從頭開始一架一櫃的逐本查詢,運氣好的話,在第一個書架的第一本書就找到了,運氣不好的話,要到最後一個書架的最後一本書才找到。

SQLS在接到查詢請求的時候,首先會分析sysindexes表中一個叫做索引標誌符(INDID: Index ID)的欄位的值,如果該值為0,表示這是一張資料表而不是索引表,SQLS就會使用sysindexes表的另一個欄位——也就是在前面提到過的FirstIAM值中找到該表的IAM頁鏈——也就是所有資料頁集合。

這就是對一個沒有建立索引的資料表進行資料查詢的方式,是不是很沒效率?對於沒有索引的表,對於一“堆”這樣的記錄,SQLS也只能這樣做,而且更沒勁的是,即使在第一行就找到了被查詢的記錄,SQLS仍然要從頭到尾的將表掃描一次。這種查詢稱為“遍歷”,又叫“表掃描”。

可見沒有建立索引的資料表照樣可以執行,不過這種方法對於小規模的表來說沒有什麼太大的問題,但要查詢海量的資料效率就太低了。

(二)SQLS怎樣訪問建立了非聚集索引的資料表:

如前所述,非聚集索引可以建多個,具有B樹結構,其葉級節點不包含資料頁,只包含索引行。假定一個表中只有非聚集索引,則每個索引行包含了非聚集索引鍵值以及行定位符(ROW ID,RID),他們指向具有該鍵值的資料行。每一個RID由檔案ID、頁編號和在頁中行的編號組成。

當INDID的值在2-250之間時,意味著表中存在非聚集索引頁。此時,SQLS呼叫ROOT欄位的值指向非聚集索引B樹的ROOT,在其中查詢與被查詢最相近的值,根據這個值找到在非葉級節點中的頁號,然後順藤摸瓜,在葉級節點相應的頁面中找到該值的RID,最後根據這個RID在Heap中定位所在的頁和行並返回到查詢端。

例如:假定在Lastname上建立了非聚集索引,則執行Select * From Member Where Lastname=’Ota’時,查詢過程是:①SQLS查詢INDID值為2;②立即從根出發,在非葉級節點中定位最接近Ota的值“Martin”,並查到其位於葉級頁面的第61頁;③僅在葉級頁面的第61頁的Martin下搜尋Ota的RID,其RID顯示為N∶706∶4,表示Lastname欄位中名為Ota的記錄位於堆的第707頁的第4行,N表示檔案的ID值,與資料無關;④根據上述資訊,SQLS立馬在堆的第 707頁第4行將該記錄“揪”出來並顯示於前臺(客戶端)。視表的資料量大小,整個查詢過程費時從百分之幾毫秒到數毫秒不等。

在談到索引基本概念的時候,我們就提到了這種方式:

圖書館的前臺有很多索引卡片櫃,裡面分了若干的類別,諸如按照書名筆畫或拼音順序、作者筆畫或拼音順序等等,但不同之處有二:① 索引卡片上記錄了每本書擺放的具體位置——位於某櫃某架的第幾本——而不是“特殊編號”;② 書脊上並沒有那個“特殊編號”。管理員在索引櫃中查到所需圖書的具體位置(RID)後,根據RID直接在書庫中的具體位置將書提出來。

顯然,這種查詢方式效率很高,但資源佔用極大,因為書庫中書的位置隨時在發生變化,必然要求管理員花費額外的精力和時間隨時做好索引更新。

(三)SQLS怎樣訪問建立了聚集索引的資料表:

在聚集索引中,資料所在的資料頁是葉級,索引資料所在的索引頁是非葉級。

查詢原理和上述對非聚集索引的查詢相似,但由於記錄是按照聚集索引中索引鍵值進行排序,換句話說,聚集索引的索引鍵值也就是具體的資料頁。

這就好比書庫中的書就是按照書名的拼音在排序,而且也只按照這一種排序方式建立相應的索引卡片,於是查詢起來要比上述只建立非聚集索引的方式要簡單得多。仍以上面的查詢為例:

假定在Lastname欄位上建立了聚集索引,則執行Select * From Member Where Lastname=’Ota’時,查詢過程是:①SQLS查詢INDID值為1,這是在系統中只建立了聚集索引的標誌;②立即從根出發,在非葉級節點中定位最接近Ota的值“Martin”,並查到其位於葉級頁面的第120頁;③在位於葉級頁面第120頁的Martin下搜尋到Ota條目,而這一條目已是資料記錄本身;④將該記錄返回客戶端。

這一次的效率比第二種方法更高,以致於看起來更美,然而它最大的優點也恰好是它最大的缺點——由於同一張表中同時只能按照一種順序排列,所以在任何一種資料表中的聚集索引只能建立一個;並且建立聚集索引需要至少相當於源表120%的附加空間,以存放源表的副本和索引中間頁!

難道魚和熊掌就不能兼顧了嗎?辦法是有的。

(四)SQLS怎樣訪問既有聚集索引、又有非聚集索引的資料表:

如果我們在建立非聚集索引之前先建立了聚集索引的話,那麼非聚集索引就可以使用聚集索引的關鍵字進行檢索,就像在圖書館中,前臺卡片櫃中的可以有不同類別的圖書索引卡,然而每張卡片上都載明瞭那個特殊編號——並不是書籍存放的具體位置。這樣在最大程度上既照顧了資料檢索的快捷性,又使索引的日常維護變得更加可行,這是最為科學的檢索方法。

也就是說,在只建立了非聚集索引的情況下,每個葉級節點指明瞭記錄的行定位符(RID);而在既有聚集索引又有非聚集索引的情況下,每個葉級節點所指向的是該聚集索引的索引鍵值,即資料記錄本身。

假設聚集索引建立在Lastname上,而非聚集索引建立在Firstname上,當執行Select * From Member Where Firstname=’Mike’時,查詢過程是:①SQLS查詢INDID值為2;②立即從根出發,在Firstname的非聚集索引的非葉級節點中定位最接近Mike的值“Jose”條目;③從Jose條目下的葉級頁面中查到Mike邏輯位置——不是RID而是聚集索引的指標;④根據這一指標所指示位置,直接進入位於Lastname的聚集索引中的葉級頁面中到達Mike資料記錄本身;⑤將該記錄返回客戶端。

這就完全和我們在“索引的基本概念”中講到的現實場景完全一樣了,當資料發生更新的時候,SQLS只負責對聚集索引的健值駕以維護,而不必考慮非聚集索引,只要我們在ID類的欄位上建立聚集索引,而在其它經常需要查詢的欄位上建立非聚集索引,通過這種科學的、有針對性的在一張表上分別建立聚集索引和非聚集索引的方法,我們既享受了索引帶來的靈活與快捷,又相對規避了維護索引所導致的大量的額外資源消耗。

六、索引的優點和不足

索引有一些先天不足:1:建立索引,系統要佔用大約為表的1.2倍的硬碟和記憶體空間來儲存索引。2:更新資料的時候,系統必須要有額外的時間來同時對索引進行更新,以維持資料和索引的一致性——這就如同圖書館要有專門的位置來擺放索引櫃,並且每當庫存圖書發生變化時都需要有人將索引卡片重整以保持索引與庫存的一致。

當然建立索引的優點也是顯而易見的:在海量資料的情況下,如果合理的建立了索引,則會大大加強SQLS執行查詢、對結果進行排序、分組的操作效率。

實踐表明,不恰當的索引不但於事無補,反而會降低系統性能。因為大量的索引在進行插入、修改和刪除操作時比沒有索引花費更多的系統時間。比如在如下欄位建立索引應該是不恰當的:1、很少或從不引用的欄位;2、邏輯型的欄位,如男或女(是或否)等。

綜上所述,提高查詢效率是以消耗一定的系統資源為代價的,索引不能盲目的建立,必須要有統籌的規劃,一定要在“加快查詢速度”與“降低修改速度”之間做好平衡,有得必有失,此消則彼長。這是考驗一個DBA是否優秀的很重要的指標。

至此,我們一直在說SQLS在維護索引時要消耗系統資源,那麼SQLS維護索引時究竟消耗了什麼資源?會產生哪些問題?究竟應該才能優化欄位的索引?

在上篇中,我們就索引的基本概念和資料查詢原理作了詳細闡述,知道了建立索引時一定要在“加快查詢速度”與“降低修改速度”之間做好平衡,有得必有失,此消則彼長。那麼,SQLS維護索引時究竟怎樣消耗資源?應該從哪些方面對索引進行管理與優化?以下就從七個方面來回答這些問題。

一、頁分裂

微軟MOC教導我們:當一個數據頁達到了8K容量,如果此時發生插入或更新資料的操作,將導致頁的分裂(又名頁拆分):

1、有聚集索引的情況下:聚集索引將被插入和更新的行指向特定的頁,該頁由聚集索引關鍵字決定;

2、只有堆的情況下:只要有空間就可以插入新的行,但是如果我們對行資料的更新需要更多的空間,以致大於了當前頁的可用空間,行就被移到新的頁中,並且在原位置留下一個轉發指標,指向被移動的新行,如果具有轉發指標的行又被移動了,那麼原來的指標將重新指向新的位置;

3、如果堆中有非聚集索引,那麼儘管插入和更新操作在堆中不會發生頁分裂,但是在非聚集索引上仍然產生頁分裂。

無論有無索引,大約一半的資料將保留在老頁面,而另一半將放入新頁面,並且新頁面可能被分配到任何可用的頁。所以,頻繁頁分裂,後果很嚴重,將使物理表產生大量資料碎片,導致直接造成I/O效率的急劇下降,最後,停止SQLS的執行並重建索引將是我們的唯一選擇!

二、填充因子

然而在“混沌之初”,就可以在一定程度上避免不愉快出現:在建立索引時,可以為這個索引指定一個填充因子,以便在索引的每個葉級頁面上保留一定百分比的空間,將來資料可以進行擴充和減少頁分裂。填充因子是從0到100的百分比數值,設為100時表示將資料頁填滿。只有當不會對資料進行更改時(例如只讀表中)才用此設定。值越小則資料頁上的空閒空間越大,這樣可以減少在索引增長過程中進行頁分裂的需要,但這一操作需要佔用更多的硬碟空間。

填充因子只在建立索引時執行,索引建立以後,當表中進行資料的新增、刪除或更新時,是不會保持填充因子的,如果想在資料頁上保持額外的空間,則有悖於使用填充因子的本意,因為隨著資料的輸入,SQLS必須在每個頁上進行頁拆分,以保持填充因子指定的空閒空間。因此,只有在表中的資料進行了較大的變動,才可以填充資料頁的空閒空間。這時,可以從容的重建索引,重新指定填充因子,重新分佈資料。

反之,填充因子指定不當,就會降低資料庫的讀取效能,其降低量與填充因子設定值成反比。例如,當填充因子的值為50時,資料庫的讀取效能會降低兩倍!所以,只有在表中根據現有資料建立新索引,並且可以預見將來會對這些資料進行哪些更改時,設定填充因子才有意義。

三、兩道數學題

假定資料庫設計沒有問題,那麼是否象上篇中分析的那樣,當你建立了眾多的索引,在查詢工作中SQLS就只能按照“最高指示”用索引處理每一個提交的查詢呢?答案是否定的!

上篇“資料是怎樣被訪問的”章節中提到的四種索引方案只是一種靜態的、標準的和理論上的分析比較,實際上,將在外,軍令有所不從,SQLS幾乎完全是“自主”的決定是否使用索引或使用哪一個索引!

這是怎麼回事呢?

讓我們先來算一道題:如果某表的一條記錄在磁碟上佔用1000位元組(1K)的話,我們對其中10位元組的一個欄位建立索引,那麼該記錄對應的索引大小隻有10位元組(0.01K)。上篇說過,SQLS的最小空間分配單元是“頁(Page)”,一個頁面在磁碟上佔用8K空間,所以一頁只能儲存8條“記錄”,但可以儲存800條“索引”。現在我們要從一個有8000條記錄的表中檢索符合某個條件的記錄(有Where子句),如果沒有索引的話,我們需要遍歷8000條×1000位元組/8K位元組=1000個頁面才能夠找到結果。如果在檢索欄位上有上述索引的話,那麼我們可以在8000條×10位元組/8K位元組=10個頁面中就檢索到滿足條件的索引塊,然後根據索引塊上的指標逐一找到結果資料塊,這樣I/O訪問量肯定要少得多。

然而有時用索引還不如不用索引快!

同上,如果要無條件檢索全部記錄(不用Where子句),不用索引的話,需要訪問8000條×1000位元組/8K位元組=1000個頁面;而使用索引的話,首先檢索索引,訪問8000條×10位元組/8K位元組=10個頁面得到索引檢索結果,再根據索引檢索結果去對應資料頁面,由於是檢索全部資料,所以需要再訪問8000條×1000位元組/8K位元組=1000個頁面將全部資料讀取出來,一共訪問了1010個頁面,這顯然不如不用索引快。

SQLS內部有一套完整的資料索引優化技術,在上述情況下,SQLS會自動使用表掃描的方式檢索資料而不會使用任何索引。那麼SQLS是怎麼知道什麼時候用索引,什麼時候不用索引的呢?因為SQLS除了維護資料資訊外,還維護著資料統計資訊!

四、統計資訊

開啟企業管理器,單擊“Database”節點,右擊Northwind資料庫→單擊“屬性”→選擇“Options”選項卡,觀察“Settings”下的各項複選項,你發現了什麼?

從Settings中我們可以看到,在資料庫中,SQLS將預設的自動建立和更新統計資訊,這些統計資訊包括資料密度和分佈資訊,正是它們幫助SQLS確定最佳的查詢策略:建立查詢計劃和是否使用索引以及使用什麼樣的索引。

在建立索引時,SQLS會建立分佈資料頁來存放有關索引的兩種統計資訊:分佈表和密度表。查詢優化器使用這些統計資訊估算使用該索引進行查詢的成本(Cost),並在此基礎上判斷該索引對某個特定查詢是否有用。

隨著表中的資料發生變化,SQLS自動定期更新這些統計資訊。取樣是在各個資料頁上隨機進行。從磁碟讀取一個數據頁後,該資料頁上的所有行都被用來更新統計資訊。統計資訊更新的頻率取決於欄位或索引中的資料量以及資料更改量。比如,對於有一萬條記錄的表,當1000個索引鍵值發生改變時,該表的統計資訊便可能需要更新,因為1000 個值在該表中佔了10%,這是一個很大的比例。而對於有1千萬條記錄的表來說,1000個索引值發生更改的意義則可以忽略不計,因此統計資訊就不會自動更新。

至於它們幫助SQLS建立查詢計劃的具體過程,限於篇幅,這裡就省略了,請有興趣的朋友們自己研究。

順便多說一句,SQLS除了能自動記錄統計資訊之外,還可以記錄伺服器中所發生的其它活動的詳細資訊,包括I/O 統計資訊、CPU 統計資訊、鎖定請求、T-SQL 和 RPC 統計資訊、索引和表掃描、警告和引發的錯誤、資料庫物件的建立/除去、連線/斷開、儲存過程操作、遊標操作等等。這些資訊的讀取、設定請朋友們在SQLS聯機幫助文件(SQL Server Books Online)中搜索字串“Profiler”查詢。

五、索引的人工維護

上面講到,某些不合適的索引將影響到SQLS的效能,隨著應用系統的執行,資料不斷地發生變化,當資料變化達到某一個程度時將會影響到索引的使用。這時需要使用者自己來維護索引。

隨著資料行的插入、刪除和資料頁的分裂,有些索引頁可能只包含幾頁資料,另外應用在執行大量I/O的時候,重建非聚聚集索引可以維護I/O的效率。重建索引實質上是重新組織B樹。需要重建索引的情況有:

1) 資料和使用模式大幅度變化;

2)排序的順序發生改變;

3)要進行大量插入操作或已經完成;

4)使用I/O查詢的磁碟讀次數比預料的要多;

5)由於大量資料修改,使得資料頁和索引頁沒有充分使用而導致空間的使用超出估算;

6)dbcc檢查出索引有問題。

六、索引的使用原則

接近尾聲的時候,讓我們再從另一個角度認識索引的兩個重要屬性----唯一性索引和複合性索引。

在設計表的時候,可以對欄位值進行某些限制,比如可以對欄位進行主鍵約束或唯一性約束。

主鍵約束是指定某個或多個欄位不允許重複,用於防止表中出現兩條完全相同的記錄,這樣的欄位稱為主鍵,每張表都可以建立並且只能建立一個主鍵,構成主鍵的欄位不允許空值。例如職員表中“身份證號”欄位或成績表中“學號、課程編號”欄位組合。

而唯一性約束與主鍵約束類似,區別只在於構成唯一性約束的欄位允許出現空值。

建立在主鍵約束和唯一性約束上的索引,由於其欄位值具有唯一性,於是我們將這種索引叫做“唯一性索引”,如果這個唯一性索引是由兩個以上欄位的組合建立的,那麼它又叫“複合性索引”。

注意,唯一索引不是聚集索引,如果對一個欄位建立了唯一索引,你僅僅不能向這個欄位輸入重複的值。並不妨礙你可以對其它型別的欄位也建立一個唯一性索引,它們可以是聚集的,也可以是非聚集的。

唯一性索引保證在索引列中的全部資料是唯一的,不會包含冗餘資料。如果表中已經有一個主鍵約束或者唯一性約束,那麼當建立表或者修改表時,SQLS自動建立一個唯一性索引。但出於必須保證唯一性,那麼應該建立主鍵約束或者唯一性鍵約束,而不是建立一個唯一性索引。當建立唯一性索引時,應該認真考慮這些規則:當在表中建立主鍵約束或者唯一性鍵約束時, SQLS鈄自動建立一個唯一性索引;如果表中已經包含有資料,那麼當建立索引時,SQLS檢查表中已有資料的冗餘性,如果發現冗餘值,那麼SQLS就取消該語句的執行,並且返回一個錯誤訊息,確保表中的每一行資料都有一個唯一值。

複合索引就是一個索引建立在兩個列或者多個列上。在搜尋時,當兩個或者多個列作為一個關鍵值時,最好在這些列上建立複合索引。當建立複合索引時,應該考慮這些規則:最多可以把16個列合併成一個單獨的複合索引,構成複合索引的列的總長度不能超過900位元組,也就是說複合列的長度不能太長;在複合索引中,所有的列必須來自同一個表中,不能跨表建立複合列;在複合索引中,列的排列順序是非常重要的,原則上,應該首先定義最唯一的列,例如在(COL1,COL2)上的索引與在(COL2,COL1)上的索引是不相同的,因為兩個索引的列的順序不同;為了使查詢優化器使用複合索引,查詢語句中的WHERE子句必須參考複合索引中第一個列;當表中有多個關鍵列時,複合索引是非常有用的;使用複合索引可以提高查詢效能,減少在一個表中所建立的索引數量。

綜上所述,我們總結了如下索引使用原則:

1)邏輯主鍵使用唯一的成組索引,對系統鍵(作為儲存過程)採用唯一的非成組索引,對任何外來鍵列採用非成組索引。考慮資料庫的空間有多大,表如何進行訪問,還有這些訪問是否主要用作讀寫。

2)不要索引memo/note 欄位,不要索引大型欄位(有很多字元),這樣作會讓索引佔用太多的儲存空間。

3)不要索引常用的小型表

4)一般不要為小型資料表設定過多的索引,假如它們經常有插入和刪除操作就更別這樣作了,SQLS對這些插入和刪除操作提供的索引維護可能比掃描表空間消耗更多的時間。

七、大結局

查詢是一個物理過程,表面上是SQLS在東跑西跑,其實真正大部分壓馬路的工作是由磁碟輸入輸出系統(I/O)完成,全表掃描需要從磁碟上讀表的每一個數據頁,如果有索引指向資料值,則I/O讀幾次磁碟就可以了。但是,在隨時發生的增、刪、改操作中,索引的存在會大大增加工作量,因此,合理的索引設計是建立在對各種查詢的分析和預測上的,只有正確地使索引與程式結合起來,才能產生最佳的優化方案。

一般來說建立索引的思路是:

(1)主鍵時常作為where子句的條件,應在表的主鍵列上建立聚聚集索引,尤其當經常用它作為連線的時候。

(2)有大量重複值且經常有範圍查詢和排序、分組發生的列,或者非常頻繁地被訪問的列,可考慮建立聚聚集索引。  

(3)經常同時存取多列,且每列都含有重複值可考慮建立複合索引來覆蓋一個或一組查詢,並把查詢引用最頻繁的列作為前導列,如果可能儘量使關鍵查詢形成覆蓋查詢。

(4)如果知道索引鍵的所有值都是唯一的,那麼確保把索引定義成唯一索引。

(5)在一個經常做插入操作的表上建索引時,使用fillfactor(填充因子)來減少頁分裂,同時提高併發度降低死鎖的發生。如果在只讀表上建索引,則可以把fillfactor置為100。

(6)在選擇索引欄位時,儘量選擇那些小資料型別的欄位作為索引鍵,以使每個索引頁能夠容納儘可能多的索引鍵和指標,通過這種方式,可使一個查詢必須遍歷的索引頁面降到最小。此外,儘可能地使用整數為鍵值,因為它能夠提供比任何資料型別都快的訪問速度。

SQLS是一個很複雜的系統,讓索引以及查詢背後的東西真相大白,可以幫助我們更為深刻的瞭解我們的系統。一句話,索引就象鹽,少則無味多則鹹。