T-SQL查詢——深入理解索引,原理(B樹)


 

   在SQL Server中,索引是一種增強式的存在,這意味着,即使沒有索引,SQL Server仍然可以實現應有的功能。但索引可以在大多數情況下大大提升查詢性能高。在OLAP中尤其明顯,要完全理解索引的概念,需要了解大量原理性的知識,包括B樹,堆,數據庫頁,區,填充因子,碎片,文件組等等一系列相關知識。

  索引時對數據庫中表中一列和多列的值進行排序的一種結構,使用索引可以快速訪問數據表中特定的信息。

  精簡來說,索引時一種結構,在SQL Server中,索引和表(這里值得是加了聚集索引的表)的存儲結構是一樣的,都是B樹,B樹是一種用於查找平衡多叉樹,理解B樹的概念如下圖:

  理解為什么使用B樹作為索引和表(有聚集索引)的結構,首先需要理解SQL Server存儲數據的原理。

   B樹,即二叉搜索樹,結構特點:

   1、所有非葉子節點至多擁有兩個子節點(Left和Right)

   2、所有的節點存儲一個關鍵字

   3、非頁子節點的左指針指向小於其關鍵字的子樹,右節點指向大於其關鍵字的子樹

   看下面結構圖:

B樹的搜索,從根節點開始,如果查詢的關鍵字與結點相等,那么就命中,否則,如果查詢的關鍵字比結點關鍵字小,則進入左節點,如果比關鍵字大,就進入右結點;如果左結點或右結點指針為空,則報告找不到相應的關鍵字。

如果B樹的所有非葉子結點的左右子樹的節點數目保持差不多(平衡),那么B樹的搜索性能逼近二分查找,但它比連續內存空間的二分查找的優點是:改變B樹結構(插入與刪除結點)不需要移動大段的內存數據,甚至通常是常數開銷,這也是數據表和索引采取這種方式存儲的有點。

但這樣,也會帶來相應的缺點,就是B樹經過多次插入和刪除后,有可能導致結構的變化:

   右邊也是一個B樹,但它的索性能已經是線性的了,同樣的關鍵字集合有可能導致不同的樹結構索引,所以,使用B樹還要考慮盡可能讓B樹保持左圖的結構,和避免右圖的結構,這就出現了平衡二叉樹的算法。

   實際的使用B樹都是在B樹的基礎上加上平衡算法,即“平衡二叉樹”;如果保持B樹節點分布均勻的平衡算法是平衡二叉樹的關鍵。平衡算法是一種在B樹中插入和刪除結點的策略;

  另一種是多路搜索樹(並不是二叉的)該樹所有的特點是:

   1、定義任意非葉子子節點最多只有M個子節點,且M>2

   2、根節點的子節點數為[2,M]

   3、除根節點意外的非葉子結點的子節點數為[M/2,M]

   4、每個結點存放至少M/2-1(取上整)和至多M-1個關鍵字;(至少兩個關鍵字)

   5、非葉子節點的關鍵字個數=指向子節點的指針個數-1

   6、非葉子結點的關鍵字:k[1],k[2]......k[M-1],且k[i]<k[i+1]

   7、非葉子結點的指針:p[1],p[2]....p[M];其中p[1]指向關鍵字k[1]的子樹,p[M]指向關鍵字大於K[M-1]的子樹,其他p[i]指向關鍵字屬於(K[i-1],K[i])的子樹;

   8、所有葉子結點位於同一層 如:(M=3)

 B樹的搜索,從根結點開始,對節點內的關鍵字(有序)序列進行二分查找,如果命中則結束,否則進入查詢關鍵字所屬范圍的兒子結點;重復,直到所對應的兒子指針為空,或已經是葉子結點;

B樹的特性:

1、關鍵字集合分布在整棵樹中;

2、任何一個關鍵字出現而且只出現在一個結點中

3、搜索有可能在非葉子結點結束

4、其搜索性能等價於在關鍵字全集內做一次二分查找

5、自動層次控制

由於限制了除結點以外的非葉子結點,至少含有M/2個兒子,確保了結點的至少利用率。其最低搜索性能為:

其中,M為設定的非葉子結點最多子樹個數,N為關鍵字總數,所以B-樹的性能總是等價於二分查找(與M值無關),也就是沒有B樹平衡的問題,由於M/2的限制,在插入結點時,如果結點已滿,需要將結點分裂為兩個各占M/2及誒單,刪除結點時,需要將兩個不足M/2結點合並,確保平衡。

  有點復雜,說實話個人也不了解,大體懂得這種結構的存儲形式就可以,在SQL Server中,存儲的單位最小的是頁(page),頁是不可再分的。原子性,這就意味着SQL Server中對於頁的讀取,要不整個讀取,要么完全不讀取,沒有折中方案。

  在數據庫檢索來說,對於磁盤IO掃描時最消耗時間的,因為磁盤掃描涉及很多物理特性,這些是相當消耗時間的。所以B樹涉及的初衷就是最大限度的減少對於磁盤的掃描次數。如果一個表或索引沒有使用B樹(對於沒有聚集索引的表是用堆heap存儲),那么查找一個數據,需要在整個表包含的數據庫頁中全盤掃描。這無疑會大大加重IO負擔,而在SQL Server中使用B樹進行存儲,則僅僅需要將B樹的根節點存入內存中,經過幾次查找后就可以找到存放需要數據的被葉子結點包含的頁,進而避免了全盤掃描從而提高了性能。

下面,通過一個例子來證明:

在SQL Server中,表上如果沒有建立聚集索引,則是按照堆Heap存放的,假設我有這樣一張表

現在這種表上沒有任何索引,也就是以堆存放,我們通過在其上加上聚集索引(以B樹存放)來展現對IO的減少:

理解聚集和聚集索引

   在SQL Server中,最主要的兩類索引時聚集索引和非聚集索引。可以看到,這兩個分類都是圍繞聚集這個關鍵字進行的,那么首先理解什么是聚集。

   聚集在索引中的定義:

   為了提高某個屬性(或屬性組)的查詢速度,把這個或這些屬性(成為聚集碼)上具有相同值得元組集合中存放在連續的物理塊成為聚集。

   簡單來說,聚集索引就是:

在SQL Server中,聚集的作用就是將某一列(或是多列)的物理順序改變為和邏輯順序相一致,比如,我從adventureworks數據庫中的employee抽取5條數據:

當我在ContactID上建立聚集索引時,再次查詢:

在SQL Server中,聚集索引的存儲是以B樹存儲,B樹的葉子直接存儲聚集索引的數據:

因為聚集索引改變的是其所在表的物理存儲順序,所以每個表只能有一個聚集索引。

非聚集索引

    因為每個表只能有一個聚集索引,如果我們對一個表的查詢不僅僅限於聚集索引上的字段。我們又對聚集索引列之外還有索引的要求,那么就需要非聚集索引了。

    非聚集索引,本質上來說也是聚集索引的一種,非聚集索引並不改變其所在表的物理結構,而是額外生成一個聚集索引的B樹結構,但葉節點是對於其所在表的引用,這個引用分為兩種,如果其所在表上沒有聚集索引,則引用行號;如果其所在表上已經有了聚集索引,則引用聚集索引的頁,從而實現更大限度的使用。

   一個簡單的非聚集索引概念如下:

可以看到,非聚集索引需要額外的空間進行空間存儲,按照被索引列進行聚集索引,並在B樹的葉子結點包含指向非聚集索引所在的指針。

可以看到,非聚集索引也是一個B樹結構,與聚集索引不同的是,B樹的葉子結點存的是指向堆或聚集索引的指針。

通過非聚集索引的原理可以看出,如果其所在表的物理結構改變后,比如加上或是刪除聚集索引,那么所有非聚集索引都需要被重建,這個對於性能的耗損是相當大的。所以最好要建立聚集索引,再建立對應的非聚集索引。

聚集索引VS非聚集索引

   前面通過對於聚集索引和非聚集索引的原理解釋,我們不難發現,大多數情況下,聚集索引的速度比非聚集索引要略快一些,因為聚集索引的B樹葉子節點直接存放數據,而非聚集索引還需要額外通過葉子節點的指針找到數據。

   還有,對於大量連續數據查找,非聚集索引十分乏力,因為非聚集索引需要在非聚集索引的B樹中找到每一行的指針,再去其所在表上找數據,性那因此會大打折扣,有時候甚至不如不加非聚集索引。

   因此,大多數情況下非聚集索引都要快於非聚集索引。但聚集索引只能有一個,因此選對聚集索引所施加的列對於查詢性能提升至關緊要。

填充因子

  在B樹組作為索引的物理存儲結構的時候,這里面還涉及一個新的概念:填充因子,因為從上面的B樹結構看以看出,當數據表在面積的刪除和增加的時候,需要動態的修改B樹中的索引結構,為了實現B樹的平衡,達到搜索二分法優化查詢的作用,需要在B樹非頁結點中每個結點都留出一定的空間來記錄新數據或者描述刪除數據,這一部分就被稱作填充因子,看MSDN中的解釋:

提供填充因子選項是為了優化索引存儲和性能。當創建或重新生成索引時,填充因子的值可確定每個頁級頁上要填充數據的空間百分比,以便在每一頁上保留一些剩余空間作為以后擴展索引的可用空間。例如,指定填充因子的值為80表示每個葉級頁上將有20%的空間保留為空,以便隨着向基礎表中添加數據而為擴展索引提供空間。在索引之間保留可用空間,而不是在索引的末尾保留。但這里需要注意,當填充因子為0並不表示頁全部為數據,反而表示完全填充級頁。填充因子的值得是1到100之間的百分比。

索引的使用

   索引的使用並不需要顯示使用,建立索引后查詢分析器會自動找到最短路徑使用索引。

   但是這種情況,當隨着數據量的增長,產生了索引碎片,很多存儲的數據進行了不適當的跨頁,會造成碎片,所以我們需要重新建立索引加快性能。

   比如前面的Test_tb2上建立的一個聚集索引和非聚集索引,可以通過DMV語句查詢器索引情況:

SELECT index_type_desc,alloc_unit_type_desc,avg_fragmentation_in_percent,fragment_count,avg_fragment_size_in_pages,page_count,record_count,avg_page_space_used_in_percent
FROM sys.dm_db_index_physical_stats(DB_ID('AdventureWorks'),OBJECT_ID('test_tb2'),NULL,NULL,'Sampled')

我們可以通過重建索引來提高速度:

ALTER INDEX idx_text_tb2_EmployeeID ON test_tb2 REBUILD

還有一種情況是,當隨着表數據量的增大,有時候需要更新表上的統計信息,讓查詢分析器根據這些信息選擇路徑,使用:

UPDATE STATISTICS 表名

那么什么時候知道需要更新這些統計信息呢,就是當執行計划中估計行數和實際表的行數有出入時:

索引的代價

    當然索引的使用也是要付出代價的:

   1、通過聚集索引的原理我們知道,當表建立索引后,就可以B樹來存儲數據,所以當對其進行更新插入刪除,就需要頁在物理上移動以調整B樹,因此當更新插入刪除數據時,會帶來性能的下降。而對於非聚集索引,當更新表后,非聚集索引需要進行更新,相當於多更新了N(N=非聚集索引數量)個表。因此也下降了性能。

  2、通過上面對非聚集索引原理的介紹,可以看到,非聚集索引需要額外的磁盤空間。

  3、前文提過,不恰當的非聚集索引反而會減低性能。

 所以使用索引需要根據實際情況進行權衡.通常我都會將非聚集索引全部放到另外一個獨立硬盤上,這樣可以分散IO,從而使查詢並行.

轉自http://www.cnblogs.com/CareySon/archive/2011/12/22/2297568.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM