本文關(guān)注以下方面(本文所有的討論基于SQL Server數(shù)據(jù)庫):
一、索引定義分類
讓我們先來回答幾個問題:
二、索引數(shù)據(jù)結(jié)構(gòu)
在SQL Server數(shù)據(jù)庫中,索引的存儲是以B+樹(注意和二叉樹的區(qū)別)結(jié)構(gòu)來存儲的,又稱索引樹,其節(jié)點類型為如下兩種:
索引節(jié)點按照層級關(guān)系,有時又可以分為根節(jié)點和中間節(jié)點,其本質(zhì)是一樣的,都只包含下一層節(jié)點的入口值和入口指針;
葉子節(jié)點就不同了,它包含數(shù)據(jù),這個數(shù)據(jù)可能是表中真實的數(shù)據(jù)行,也有可能是索引列值和行書簽,前者對應(yīng)于聚集索引,后者對應(yīng)于非聚集索引。
三、索引存儲結(jié)構(gòu)
在正式討論索引的存儲結(jié)構(gòu)之前,我們有必要先來了解一下SQL Server數(shù)據(jù)庫的存儲結(jié)構(gòu)。
SQL Server數(shù)據(jù)庫存儲(結(jié)構(gòu))的最小單位是頁,大小為8K,共8 * 1024 = 8192Byte,不論是數(shù)據(jù)頁還是索引頁都是以此方式存放。實際上對于SQL Server數(shù)據(jù)庫而言,其頁(Page)類型有很多種,大概有如下十幾種(http://www.sqlnotes.info/2011/10/31/page-type/):
表中所有數(shù)據(jù)頁的存放在磁盤上又有兩種組織方式:
如果表中所有數(shù)據(jù)頁是以一種頁間無序、隨機存儲的方式,則稱這樣的表為堆表;
否則如果表中數(shù)據(jù)頁間按某種方式(如表中某個字段)有序地存儲與磁盤上,則稱為索引組織表。
四、聚集索引
下面我們將深入研究一下數(shù)據(jù)庫中的索引到底是如何存儲的以及如何被使用的。
為了測試驗證等,我們在數(shù)據(jù)庫PCT上新建一張測試表Employee,有兩個字段,其中EmployeeId為主鍵
USE PCTCREATE TABLE Employee ( EmployeeId NVARCHAR(32) NOT NULL PRIMARY KEY, EmployeeName NVARCHAR(40) NOT NULL,);
插入10W筆測試數(shù)據(jù)
SET NOCOUNT ONdeclare @i intset @i=1while @i<=100000begin INSERT INTO Employee VALUES(replace(newid(), '-', ''), 'Employee_' + CONVERT(varchar, @i) );set @i = @i+1end
通過DBCC IND命令來查看索引的情況
DBCC IND ([PCT], [DBO.Employee], -1)
結(jié)果如下
紅色標(biāo)記說明:
為了方便查找,我們也可以把上述結(jié)果存入表中,為此建表
CREATE TABLE DBCCIndResult ( PageFID NVARCHAR(200), PagePID NVARCHAR(200), IAMFID NVARCHAR(200), IAMPID NVARCHAR(200), ObjectID NVARCHAR(200), IndexID NVARCHAR(200), PartitionNumber NVARCHAR(200), PartitionID NVARCHAR(200), iam_chain_type NVARCHAR(200), PageType NVARCHAR(200), IndexLevel NVARCHAR(200), NextPageFID NVARCHAR(200), NextPagePID NVARCHAR(200), PrevPageFID NVARCHAR(200), PrevPagePID NVARCHAR(200))
插入數(shù)據(jù)
INSERT INTO DBCCIndResult EXEC ('DBCC IND(PCT,Employee,-1) ')
我們可以通過下面的語句來查看索引的深度
select * from sys.dm_db_index_physical_stats(db_id('PCT'),object_id('Employee'),null,null,null)
我們看到索引的深度為3,上面的IndexLevel分別有0,1,2也驗證了這一點。page_count為1944,但是我們上面查到的結(jié)果卻是1977,這是因為這里的語句沒有計算Index為1和2的頁(注意index_level列)
接下來我們看看B樹中各種節(jié)點存儲的到底是什么?
找到根節(jié)點283
select * from DBCCIndResult where pagetype = 2 and indexLevel = 2
查看頁里的數(shù)據(jù)
DBCC TRACEON (3604);GODBCC PAGE (PCT, 1, 283, 3);GO
從上圖,可以看出,此根節(jié)點共有31個兒子(中間節(jié)點),而且還存有主鍵值EmployeeId,那么這31個主鍵值是哪些記錄的主鍵值呢?我們繼續(xù)深入
以中間節(jié)點1863為例
DBCC TRACEON (3604);GODBCC PAGE (PCT, 1, 1863, 3);GO
這和根節(jié)點很類似,標(biāo)明了包含下一層的節(jié)點(共65個)和主鍵值,繼續(xù)深入
以葉節(jié)點807為例
DBCC TRACEON (3604);GODBCC PAGE (PCT, 1, 807, 3);GO
由于結(jié)果太多,我就不把所有的截圖都發(fā)出來了,但是從上面我們已經(jīng)看到了一些重要的東西
首先PAGE:(1:807)表明這是一個葉節(jié)點,同時也是一個數(shù)據(jù)頁,因為它存放了表里所有字段的數(shù)據(jù)(EmployeeId和EmployeeName),換句話說這兒的葉節(jié)點就是表Employee在數(shù)據(jù)庫中的存儲數(shù)據(jù)頁,也就是說聚集索引的葉節(jié)點其實就是表的數(shù)據(jù)存儲頁
其次我們看標(biāo)紅的EmployeeId,它就是我們在之前根節(jié)點283和中間節(jié)點1863存儲的主鍵值,而且它是位于數(shù)據(jù)存儲頁的第一個數(shù)據(jù)
至此我們總結(jié)如下:
為了更方便地查看葉節(jié)點的數(shù)據(jù),我們將其存入表中
DBCC PAGE(PCT,1,807, 3) WITH TABLERESULTS
這種方式是以表的方式展示
但是這種方式也不便查找,我們索性新建表
CREATE TABLE DBCCPageResult( ParentObject NVARCHAR(200), Object NVARCHAR(200), Field NVARCHAR(200), Value NVARCHAR(200))
插入數(shù)據(jù)
INSERT INTO DBCCPageResult EXEC ('DBCC PAGE(PCT,1,807, 3) WITH TABLERESULTS')
查看EmployeeId數(shù)據(jù)
select * from DBCCPageResult where Field = 'EmployeeId'
注意Value,是按順序排好的,這也是聚集索引的意義了 - 把數(shù)據(jù)按順序存儲.
至此我們又可以得出:
五、非聚集索引
在表Employee字段EmployeeName建立非聚集索引
CREATE NONCLUSTERED INDEX IX_TBL_Employee_EmployeeName ON Employee(EmployeeName) WITH FILLFACTOR= 30GO
再增加一列Pho
新聞熱點
疑難解答
圖片精選