[转]索引的学习
(一)SQLS如何訪問沒有建立索引的數(shù)據(jù)表
Heap譯成漢語叫做“堆”,其本義暗含雜亂無章、無序的意思,前面提到數(shù)據(jù)值被寫進(jìn)數(shù)據(jù)頁時,由于每一行記錄之間并沒有特定的排列順序,所以行與行的順序就是隨機(jī)無序的,當(dāng)然表中的數(shù)據(jù)頁也就是無序的了,而表中所有數(shù)據(jù)頁就形成了“堆”??梢哉f,一張沒有索引的數(shù)據(jù)表,就像一個只有書柜而沒有索引卡片柜的圖書館,書庫里面塞滿了一堆亂七八糟的圖書。當(dāng)讀者對管理員提交查詢請求后,管理員就一頭鉆進(jìn)書庫,對照查找內(nèi)容從頭開始一架一柜的逐本查找。運(yùn)氣好的話,在第一個書架的第一本書就 找到了,運(yùn)氣不好的話,要到最后一個書架的最后一本書才找到。
SQLS在接到查詢請求時,首先會分析sysindexes表中一個叫做索引標(biāo)志符(INDID: Index ID)的字段的值,如果該值為0,表示這是一張數(shù)據(jù)表而不是索引表,SQLS就會使用sysindexes表的另一個字段——也就是在前面提到過的FirstIAM值中找到該表的IAM頁鏈,也就是所有數(shù)據(jù)頁集合。
這就是對一個沒有建立索引的數(shù)據(jù)表進(jìn)行數(shù)據(jù)查找的方式,是不是很沒效率?對于沒有索引的表,對于一“堆”這樣的記錄,SQLS也只能這樣做,而且更沒勁的是,即使在第一行就找到了被查詢的記錄,SQLS仍然要從頭到尾的將表掃描一次。這種查詢稱為“遍歷”,又叫“表掃描”。
可見沒有建立索引的數(shù)據(jù)表照樣可以運(yùn)行,不過這種方法對于小規(guī)模的表來說沒有什么太大的問題,但要查詢海量的數(shù)據(jù)效率就太低了。
(二)SQLS如何訪問建立了非聚集索引的數(shù)據(jù)表
如前所述,非聚集索引可以建多個,具有B樹結(jié)構(gòu),其葉級節(jié)點(diǎn)不包含數(shù)據(jù)頁,只包含索引行。假定一個表中只有非聚集索引,則每個索引行包含了非聚集索引鍵值以及行定位符(ROW ID,RID),他們指向具有該鍵值的數(shù)據(jù)行,每一個RID由文件ID、頁編號和在頁中行的編號組成。
當(dāng)INDID的值在2至250之間時,意味著表中存在非聚集索引頁。此時,SQLS調(diào)用ROOT字段的值指向非聚集索引B樹的ROOT,在其中查找與被查詢最相近的值,根據(jù)這個值找到在非葉級節(jié)點(diǎn)中的頁號,然后順藤摸瓜,在葉級節(jié)點(diǎn)相應(yīng)的頁面中找到該值的RID,最后根據(jù)這個RID在Heap中定位所在的頁和行并返回到查詢端。
例如:假定在Lastname上建立了非聚集索引,則執(zhí)行Select * From Member Where Lastname=’Ota’時,查詢過程是:
①SQLS查詢INDID值為2;
?、诹⒓磸母霭l(fā),在非葉級節(jié)點(diǎn)中定位最接近Ota的值“Martin”,并查到其位于葉級頁面的第61頁;
?、蹆H在葉級頁面的第61頁的Martin下搜尋Ota的RID,其RID顯示為N∶706∶4,表示Lastname字段中名 為Ota的記錄位于堆的第706頁的第4行,N表示文件的ID值,與數(shù)據(jù)無關(guān);
④根據(jù)上述信息,SQLS立刻在堆的第706頁第4行將該記錄“揪”出來并顯示于前臺(客戶端)。視表的數(shù)據(jù)量大小,整個查詢過程費(fèi)時從百分之幾毫秒到數(shù)毫秒不等。
在談到索引基本概念的時候,我們就提到了這種方式:圖書館的前臺有很多索引卡片柜,里面分了若干的類別,諸如按照書名筆畫或拼音順序、作者筆畫或拼音順序等,但有兩點(diǎn)不同之處:
?、?索引卡片上記錄了每本書擺放的具體位置——位于某柜某架的第幾本——而不是“特殊編號”;
?、?書脊上并沒有那個“特殊編號”。管理員在索引柜中查到所需圖書的具體位置(RID)后,根據(jù)RID直接在書庫中的具體位置將書提出來。
顯然,這種查詢方式效率很高,但資源占用極大,因?yàn)闀鴰熘袝奈恢秒S時在發(fā)生變化,必然要求管理員花費(fèi)額外的精力和時間隨時做好索引更新。
(三)SQLS如何訪問建立聚集索引的數(shù)據(jù)表
在聚集索引中,數(shù)據(jù)所在的數(shù)據(jù)頁是葉級,索引數(shù)據(jù)所在的索引頁是非葉級。
查詢原理和上述對非聚集索引的查詢相似,但由于記錄是按照聚集索引中索引鍵值進(jìn)行排序,換句話說,聚集索引的索引鍵值也就是具體的數(shù)據(jù)頁。
這就好比書庫中的書就是按照書名的拼音在排序,而且也只按照這一種排序方式建立相應(yīng)的索引卡片,于是查詢起來要比上述只建立非聚集索引的方式要簡單得多。仍以上面的查詢?yōu)槔?#xff1a;
假定在Lastname字段上建立了聚集索引,則執(zhí)行Select * From Member Where Lastname=’Ota’時,查詢過程是:
?、賁QLS查詢INDID值為1,這是在系統(tǒng)中只建立了聚集索引的標(biāo)志;
②立即從根出發(fā),在非葉級節(jié)點(diǎn)中定位最接近Ota的值“Martin”,并查到其位于葉級頁面的第120頁;
?、墼谖挥谌~級頁面第120頁的Martin下搜尋到Ota條目,而這一條目已是數(shù)據(jù)記錄本身;
?、軐⒃撚涗浄祷乜蛻舳?。
這一次的效率比第二種方法更高,以致于看起來更美,然而它最大的優(yōu)點(diǎn)也恰好是它最大的缺點(diǎn)——由于同一張表中同時只能按照一種順序排列,所以在任何一種數(shù)據(jù)表中的聚集索引只能建立一個;并且建立聚集索引需要至少相當(dāng)于源表120%的附加空間,以存放源表的副本和索引中間頁。
難道魚和熊掌就不能兼顧了嗎?辦法是有的。
(四)SQLS如何訪問既有聚集索引、又有非聚集索引的數(shù)據(jù)表
如果我們在建立非聚集索引之前先建立了聚集索引的話,那么非聚集索引就可以使用聚集索引的關(guān)鍵字進(jìn)行檢索。就像在圖書館中,前臺卡片柜中可以有不同類別的圖書索引卡,然而每張卡片上都載明了那個特殊編號——并不是書籍存放的具體位置。這樣在最大程度上既照顧了數(shù)據(jù)檢索的快捷性,又使索引的日常維護(hù)變得更加可行,這是最為科學(xué)的檢索方法。
也就是說,在只建立了非聚集索引的情況下,每個葉級節(jié)點(diǎn)指明了記錄的行定位符(RID);而在既有聚集索引又有非聚集索引的情況下,每個葉級節(jié)點(diǎn)所指向的是該聚集索引的索引鍵值,即數(shù)據(jù)記錄本身。
假設(shè)聚集索引建立在Lastname上,而非聚集索引建立在Firstname上,當(dāng)執(zhí)行Select * From Member Where Firstname=’Mike’時,查詢過程是:
?、賁QLS查詢INDID值為2;
②立即從根出發(fā),在Firstname的非聚集索引的非葉級節(jié)點(diǎn)中定位最接近Mike的值“Jose”條目;
?、蹚腏ose條目下的葉級頁面中查到Mike邏輯位置——不是RID而是聚集索引的指針;
?、芨鶕?jù)這一指針?biāo)甘疚恢?#xff0c;直接進(jìn)入位于Lastname的聚集索引中的葉級頁面中到達(dá)Mike數(shù)據(jù)記錄本身;
⑤將該記錄返回客戶端。
這就完全和我們在“索引的基本概念”中講到的現(xiàn)實(shí)場景完全一樣了,當(dāng)數(shù)據(jù)發(fā)生更新的時候,SQLS只負(fù)責(zé)對聚集索引的鍵值加以維護(hù),而不必考慮非聚集索引。只要我們在ID類的字段上建立聚集索引,而在其它經(jīng)常需要查詢的字段上建立非聚集索引,通過這種科學(xué)的、有針對性的在一張表上分別建立聚集索引和非聚集索引的方法,我們既享受了索引帶來的靈活與快捷,又相對避免了維護(hù)索引所導(dǎo)致的大量的額外資源消耗。
索引的優(yōu)點(diǎn)和不足
索引有一些先天不足
1、系統(tǒng)要占用大約為表的1.2倍的硬盤和內(nèi)存空間來保存索引;
2、更新數(shù)據(jù)的時候,系統(tǒng)必須要有額外的時間來同時對索引進(jìn)行更新,以維持?jǐn)?shù)據(jù)和索引的一致性。
當(dāng)然建立索引的優(yōu)點(diǎn)也是顯而易見的,在海量數(shù)據(jù)的情況下,如果合理的建立了索引,則會大大加強(qiáng)SQLS執(zhí)行查詢、對結(jié)果進(jìn)行排序、分組的操作效率。
實(shí)踐表明,不恰當(dāng)?shù)乃饕坏谑聼o補(bǔ),反而會降低系統(tǒng)性能。因?yàn)榇罅康乃饕谶M(jìn)行插入、修改和刪除操作時比沒有索引要花費(fèi)更多的系統(tǒng)時間。
在如下字段建立索引應(yīng)該是不恰當(dāng)?shù)?#xff1a;
1、很少或從不引用的字段;
2、邏輯型的字段,如男或女(是或否)等。
綜上所述,提高查詢效率是以消耗一定的系統(tǒng)資源為代價的,索引不能盲目的建立,必須要有統(tǒng)籌的規(guī)劃,一定要在“加快查詢速度”與“降低修改速度”之間做好平衡。有得必有失,此消則彼長,這是考驗(yàn)一個DBA是否優(yōu)秀的很重要的指標(biāo)
建立索引時一定要在“加快查詢速度”與“降低修改速度”之間做好平衡,有得必有失,此消則彼長。那么,SQLS維護(hù)索引時究竟怎樣消耗資源?應(yīng)該從哪些方面對索引進(jìn)行管理與優(yōu)化?以下從六個方面來回答這些問題。
一.頁分裂
微軟MOC教導(dǎo)我們:當(dāng)一個數(shù)據(jù)頁達(dá)到了8K容量,如果此時發(fā)生插入或更新數(shù)據(jù)的操作,將導(dǎo)致頁的分裂(又名頁拆分):
1.有聚集索引的情況下:聚集索引將被插入和更新的行指向特定的頁,該頁由聚集索引關(guān)鍵字決定;
2.只有堆的情況下:只要有空間就可以插入新的行,但是如果我們對行數(shù)據(jù)的更新需要更多的空間,以致大于當(dāng)前頁的可用空間,行就被移到新的頁中,并且在原位置留下一個轉(zhuǎn)發(fā)指針,指向被移動的新行,如果具有轉(zhuǎn)發(fā)指針的行又被移動了,那么原來的指針將重新指向新的位置;
3.如果堆中有非聚集索引,那么盡管插入和更新操作在堆中不會發(fā)生頁分裂,但是在非聚集索引上仍然產(chǎn)生頁分裂。
無論有無索引,大約一半的數(shù)據(jù)將保留在老頁面,而另一半將放入新頁面,并且新頁面可能被分配到任何可用的頁。所以,頻繁頁分裂,后果很嚴(yán)重,將使物理表產(chǎn)生大量數(shù)據(jù)碎片,導(dǎo)致直接造成I/O效率的急劇下降,最后,不得不停止SQLS的運(yùn)行并重建索引。
二.填充因子
然而在“混沌之初”,就可以在一定程度上避免不愉快出現(xiàn),在創(chuàng)建索引時,可以為這個索引指定一個填充因子,以便在索引的每個葉級頁面上保留一定百分比的空間,將來數(shù)據(jù)可以進(jìn)行擴(kuò)充和減少頁分裂。填充因子是從0到100的百分比數(shù)值,設(shè)為100時表示將數(shù)據(jù)頁填滿,只有當(dāng)不會對數(shù)據(jù)進(jìn)行更改時(例如只讀表中)才用此設(shè)置。值越小則數(shù)據(jù)頁上的空閑空間越大,這樣可以減少在索引增長過程中進(jìn)行頁分裂的需要,但這一操作需要占用更多的硬盤空間。
填充因子只在創(chuàng)建索引時執(zhí)行,索引創(chuàng)建以后,當(dāng)表中進(jìn)行數(shù)據(jù)的添加、刪除或更新時,是不會保持填充因子的,如果想在數(shù)據(jù)頁上保持額外的空間,則有悖于使用填充因子的本意,因?yàn)殡S著數(shù)據(jù)的輸入,SQLS必須在每個頁上進(jìn)行頁拆分,以保持填充因子指定的空閑空間。因此,只有在表中的數(shù)據(jù)進(jìn)行了較大的變動,才可以填充數(shù)據(jù)頁的空閑空間。這時,可以從容的重建索引,重新指定填充因子,重新分布數(shù)據(jù)。
反之,填充因子指定不當(dāng),就會降低數(shù)據(jù)庫的讀取性能,其降低量與填充因子設(shè)置值成反比。例如,當(dāng)填充因子的值為50時,數(shù)據(jù)庫的讀取性能會降低兩倍。所以,只有在表中根據(jù)現(xiàn)有數(shù)據(jù)創(chuàng)建新索引,并且可以預(yù)見將來會對這些數(shù)據(jù)進(jìn)行哪些更改時,設(shè)置填充因子才有意義。
三.兩道數(shù)學(xué)題
假定數(shù)據(jù)庫設(shè)計(jì)沒有問題,那么是否像上篇分析的那樣,當(dāng)你建立了眾多的索引,在查詢工作中SQLS就只能按照“最高指示”用索引處理每一個提交的查詢呢?答案是否定的。
實(shí)際上,SQLS幾乎完全是“自主”的決定是否使用索引或使用哪一個索引。
這是怎么回事呢?
讓我們先來算一道題:如果某表的一條記錄在磁盤上占用1000字節(jié)(1K)的話,我們對其中10字節(jié)的一個字段建立索引,那么該記錄對應(yīng)的索引大小只有10字節(jié)(0.01K)。上篇說過,SQLS的最小空間分配單元是“頁(Page)”,一個頁面在磁盤上占用8K空間,所以一頁只能存儲8條“記錄”,但可以存儲800條“索引”?,F(xiàn)在我們要從一個有8000條記錄的表中檢索符合某個條件的記錄(有Where子句),如果沒有索引的話,我們需要遍歷8000條×1000字節(jié)/8K字節(jié)=1000個頁面才能夠找到結(jié)果。如果在檢索字段上有上述索引的話,那么我們可以在8000條×10字節(jié)/8K字節(jié)=10個頁面中就檢索到滿足條件的索引塊,然后根據(jù)索引塊上的指針逐一找到結(jié)果數(shù)據(jù)塊,這樣I/O訪問量肯定要少得多。
然而有時用索引比不用索引還快。
同上,如果要無條件檢索全部記錄(不用Where子句),不用索引的話,需要訪問8000條×1000字節(jié)/8K字節(jié)=1000個頁面;而使用索引的話,首先檢索索引,訪問8000條×10字節(jié)/8K字節(jié)=10個頁面得到索引檢索結(jié)果,再根據(jù)索引檢索結(jié)果去對應(yīng)數(shù)據(jù)頁面,由于是檢索全部數(shù)據(jù),所以需要再訪問8000條×1000字節(jié)/8K字節(jié)=1000個頁面將全部數(shù)據(jù)讀取出來,一共訪問了1010個頁面,這顯然不如不用索引快。
SQLS內(nèi)部有一套完整的數(shù)據(jù)索引優(yōu)化技術(shù),在上述情況下,SQLS會自動使用表掃描的方式檢索數(shù)據(jù)而不會使用任何索引。那么SQLS是怎么知道什么時候用索引,什么時候不用索引的呢?因?yàn)镾QLS除了維護(hù)數(shù)據(jù)信息外,還維護(hù)著數(shù)據(jù)統(tǒng)計(jì)信息。
四.統(tǒng)計(jì)信息
打開企業(yè)管理器,單擊“Database”節(jié)點(diǎn),右擊Northwind數(shù)據(jù)庫→單擊“屬性”→選擇“Options”選項(xiàng)卡,觀察“Settings”下的各項(xiàng)復(fù)選項(xiàng),你發(fā)現(xiàn)了什么?
從Settings中我們可以看到,在數(shù)據(jù)庫中,SQLS將默認(rèn)的自動創(chuàng)建和更新統(tǒng)計(jì)信息,這些統(tǒng)計(jì)信息包括數(shù)據(jù)密度和分布信息,正是它們幫助SQLS確定最佳的查詢策略:建立查詢計(jì)劃和是否使用索引以及使用什么樣的索引。
在創(chuàng)建索引時,SQLS會創(chuàng)建分布數(shù)據(jù)頁來存放有關(guān)索引的兩種統(tǒng)計(jì)信息:分布表和密度表。查詢優(yōu)化器使用這些統(tǒng)計(jì)信息估算使用該索引進(jìn)行查詢的成本(Cost),并在此基礎(chǔ)上判斷該索引對某個特定查詢是否有用。
隨著表中的數(shù)據(jù)發(fā)生變化,SQLS自動定期更新這些統(tǒng)計(jì)信息。采樣是在各個數(shù)據(jù)頁上隨機(jī)進(jìn)行。從磁盤讀取一個數(shù)據(jù)頁后,該數(shù)據(jù)頁上的所有行都被用來更新統(tǒng)計(jì)信息。統(tǒng)計(jì)信息更新的頻率取決于字段或索引中的數(shù)據(jù)量以及數(shù)據(jù)更改量。比如,對于有一萬條記錄的表,當(dāng)1000個索引鍵值發(fā)生改變時,該表的統(tǒng)計(jì)信息便可能需要更新,因?yàn)?000 個值在該表中占了10%,這是一個很大的比例。而對于有1千萬條記錄的表來說,1000個索引值發(fā)生更改的意義則可以忽略不計(jì),因此統(tǒng)計(jì)信息就不會自動更新。
五.索引的人工維護(hù)
上面講到,某些不合適的索引將影響到SQLS的性能,隨著應(yīng)用系統(tǒng)的運(yùn)行,數(shù)據(jù)不斷地發(fā)生變化,當(dāng)數(shù)據(jù)變化達(dá)到某一個程度時將會影響到索引的使用。這時需要用戶自己來維護(hù)索引。
隨著數(shù)據(jù)行的插入、刪除和數(shù)據(jù)頁的分裂,有些索引頁可能只包含幾頁數(shù)據(jù),另外應(yīng)用在執(zhí)行大量I/O的時候,重建非聚聚集索引可以維護(hù)I/O的效率。重建索引實(shí)質(zhì)上是重新組織B樹。需要重建索引的情況有:
1.數(shù)據(jù)和使用模式大幅度變化;
2.排序的順序發(fā)生改變;
3.要進(jìn)行大量插入操作或已經(jīng)完成;
4.使用I/O查詢的磁盤讀次數(shù)比預(yù)料的要多;
5.由于大量數(shù)據(jù)修改,使得數(shù)據(jù)頁和索引頁沒有充分使用而導(dǎo)致空間的使用超出估算;
6.dbcc檢查出索引有問題。
六.索引的使用原則
接近尾聲的時候,讓我們再從另一個角度認(rèn)識索引的兩個重要屬性----惟一性索引和復(fù)合性索引。
惟一性索引保證在索引列中的全部數(shù)據(jù)是惟一的,不會包含冗余數(shù)據(jù)。如果表中已經(jīng)有一個主鍵約束或者惟一性約束,那么當(dāng)創(chuàng)建表或者修改表時,SQLS自動創(chuàng)建一個惟一性索引。但出于必須保證惟一性,那么應(yīng)該創(chuàng)建主鍵約束或者惟一性鍵約束,而不是創(chuàng)建一個惟一性索引。
復(fù)合索引就是一個索引創(chuàng)建在兩個列或者多個列上。在搜索時,當(dāng)兩個或者多個列作為一個關(guān)鍵值時,最好在這些列上創(chuàng)建復(fù)合索引。當(dāng)創(chuàng)建復(fù)合索引時,應(yīng)該考慮這些規(guī)則:最多可以把16個列合并成一個單獨(dú)的復(fù)合索引,構(gòu)成復(fù)合索引的列的總長度不能超過900字節(jié);在復(fù)合索引中,所有的列必須來自同一個表中,不能跨表建立復(fù)合列;在復(fù)合索引中,列的排列順序是非常重要的,原則上,應(yīng)該首先定義最惟一的列,例如在(COL1,COL2)上的索引與在(COL2,COL1)上的索引是不相同的,因?yàn)閮蓚€索引的列的順序不同;為了使查詢優(yōu)化器使用復(fù)合索引,查詢語句中的WHERE子句必須參考復(fù)合索引中第一個列。
綜上所述,我們總結(jié)了如下索引使用原則:
1.邏輯主鍵使用惟一的成組索引,對系統(tǒng)鍵(作為存儲過程)采用惟一的非成組索引,對任何外鍵列采用非成組索引??紤]數(shù)據(jù)庫的空間有多大,表如何進(jìn)行訪問,還有這些訪問是否主要用作讀寫;
2.不要索引memo/note 字段,不要索引大型字段(有很多字符),這樣作會讓索引占用太多的存儲空間;
3.不要索引常用的小型表;
4.一般不要為小型數(shù)據(jù)表設(shè)置過多的索引,如果經(jīng)常有插入和刪除操作就更不要設(shè)置索引,因?yàn)镾QLS對插入和刪除操作提供的索引維護(hù)可能比掃描表空間消耗的時間更多。
查詢是一個物理過程,表面上是SQLS在東跑西跑,其實(shí)真正大部分壓馬路的工作是由磁盤輸入輸出系統(tǒng)(I/O)完成,全表掃描需要從磁盤上讀表的每一個數(shù)據(jù)頁,如果有索引指向數(shù)據(jù)值,則I/O讀幾次磁盤就可以了。但是,在隨時發(fā)生的增、刪、改操作中,索引的存在會大大增加工作量,因此,合理的索引設(shè)計(jì)是建立在對各種查詢的分析和預(yù)測上的,只有正確地使索引與程序結(jié)合起來,才能產(chǎn)生最佳的優(yōu)化方案。
SQLS是一個很復(fù)雜的系統(tǒng),讓索引以及查詢背后的東西真相大白,可以幫助我們更為深刻的了解我們的系統(tǒng)。一句話,索引就像鹽,少則無味多則咸。
-----------------------------------------來自csdn-----------------------------------------------------------
轉(zhuǎn)載于:https://www.cnblogs.com/trampt/archive/2008/06/11/1217387.html
總結(jié)
- 上一篇: 华为交换机CPU SNMP OID [简
- 下一篇: Win技巧