SQL Server2008存储结构之聚集索引
聚集索引即基于數(shù)據(jù)行的鍵值在表內(nèi)排序和存儲(chǔ)這些數(shù)據(jù)行。每個(gè)表只能有一個(gè)聚集索引,因?yàn)閿?shù)據(jù)行本身只能按一個(gè)順序存儲(chǔ)。
從某種程度上,聚集索引即數(shù)據(jù),這句話是有道理的;但正如同其他索引一樣,聚集索引也是按 B 樹結(jié)構(gòu)進(jìn)行組織的。既然是B樹組織,那么就有葉子結(jié)點(diǎn)和非葉子節(jié)點(diǎn)之分。聚集索引B 樹的頂端節(jié)點(diǎn)稱為根節(jié)點(diǎn);聚集索引中的底層節(jié)點(diǎn)稱為葉節(jié)點(diǎn)。在根節(jié)點(diǎn)與葉節(jié)點(diǎn)之間的任何索引級(jí)別統(tǒng)稱為中間級(jí)。在聚集索引中,葉節(jié)點(diǎn)包含基礎(chǔ)表的數(shù)據(jù)頁。根節(jié)點(diǎn)和中間級(jí)節(jié)點(diǎn)包含存有索引行的索引頁。每個(gè)索引行包含一個(gè)鍵值和一個(gè)指針,該指針指向 B 樹上的某一中間級(jí)頁或葉級(jí)索引中的某個(gè)數(shù)據(jù)行。每級(jí)索引中的頁均被鏈接在雙向鏈接列表中。
因此可以這么說,聚集索引的葉子結(jié)點(diǎn)存儲(chǔ)的是按聚集索引順序排列的數(shù)據(jù)本身,而中間結(jié)點(diǎn)和根節(jié)點(diǎn)則在維護(hù)索引和其層級(jí)。
對(duì)于某個(gè)聚集索引, sys.system_internals_allocation_units 中的 root_page 列指向該聚集索引某個(gè)特定分區(qū)的頂部。SQL Server 將從索引中向下移動(dòng)以查找與某個(gè)聚集索引鍵對(duì)應(yīng)的行。為了查找鍵的范圍,SQL Server 將在索引中移動(dòng)以查找該范圍的起始鍵值,然后用向前或向后指針在數(shù)據(jù)頁中進(jìn)行掃描。為了查找數(shù)據(jù)頁鏈的首頁,SQL Server 將從索引的根節(jié)點(diǎn)沿最左邊的指針進(jìn)行掃描。
| drop table testUniqueCluster drop table testNonUniqueCluster CREATE TABLE testUniqueCluster ( name CHAR(900), remark CHAR(1100) ) CREATE UNIQUE CLUSTERED INDEX ix_testUniqueCluster ON testUniqueCluster(name) INSERT INTO testUniqueCluster VALUES('B','BBB1') INSERT INTO testUniqueCluster VALUES('A','AAA1') CREATE TABLE testNonUniqueCluster ( name CHAR(900), remark CHAR(1100) ) CREATE CLUSTERED INDEX ix_testNonUniqueCluster ON testNonUniqueCluster(name) INSERT INTO testNonUniqueCluster VALUES('B','BBB2') INSERT INTO testNonUniqueCluster VALUES('B','BBB1') INSERT INTO testNonUniqueCluster VALUES('A','AAA1') SELECT c.name,a.type_desc, total_pages,used_pages,data_pages, testdb.dbo.f_get_page(first_page) first_page_address, testdb.dbo.f_get_page(root_page) root_address, testdb.dbo.f_get_page(first_iam_page) IAM_address FROM sys.system_internals_allocation_units a,sys.partitions b,sys.objects c WHERE a.container_id=b.partition_id and b.object_id=c.object_id AND c.name in ('testUniqueCluster','testNonUniqueCluster') TRUNCATE TABLE tablepage; INSERT INTO tablepage EXEC ('DBCC IND(testdb,testUniqueCluster,1)'); INSERT INTO tablepage EXEC ('DBCC IND(testdb,testNonUniqueCluster,1)'); SELECT b.name table_name, CASE WHEN c.type=0 THEN '堆' WHEN c.type=1 THEN '聚集' WHEN c.type=2 THEN '非聚集' ELSE '其他' END index_type, c.name index_name, PagePID,IAMPID,ObjectID,IndexID,Pagetype,IndexLevel, NextPagePID,PrevPagePID FROM tablepage a,sys.objects b,sys.indexes c WHERE A.ObjectID=b.object_id AND A.ObjectID=c.object_id AND a.IndexID=c.index_id |
| Name | Type_desc | Used_pages | Data_pages | First_page_address | Root_address | IAM_Address |
| testUniqueCluster | IN_ROW_DATA | 2 | 1 | 1:233 | 1:233 | 1:234 |
| testNonUniqueCluster | IN_ROW_DATA | 2 | 1 | 1:235 | 1:235 | 1:236 |
下面我們用dbcc命令介紹一下聚集索引的構(gòu)造。
| DBCC TRACEON(3604) DBCC PAGE(testDB,1,233,1) m_type = 1 5E3BC060: 1000d407 42202020 20202020 20202020 ?....B .... 5E3BC3E0: 20202020 20202020 42424231 20202020 ? BBB1 ... 5E3BC830: 20202020 0200fc10 00d40741 20202020 ? .......A ... 5E3BCBB0: 20202020 20202020 20202020 20202041 ? A 5E3BCBC0: 41413120 20202020 20202020 20202020 ?AA1 ... 5E3BD000: 20202020 20202020 20202002 00fc0000 ? ..... OFFSET TABLE: Row - Offset 1 (0x1) - 96 (0x60) 0 (0x0) - 2103 (0x837) DBCC PAGE(testDB,1,235,1) 5E3BC060: 1000d407 42202020 20202020 20202020 ?....B ... 5E3BC3E0: 20202020 20202020 42424232 20202020 ? BBB2 ... 5E3BC830: 20202020 0300f830 00d40742 20202020 ? ...0...B ... 5E3BCBB0: 20202020 20202020 20202020 20202042 ? B 5E3BCBC0: 42423120 20202020 20202020 20202020 ?BB1 ... 5E3BD000: 20202020 20202020 20202003 00f80100 ? ..... 5E3BD010: df070100 00001000 d4074120 20202020 ?..........A ... 5E3BD390: 20202020 20202020 20202020 20204141 ? AA 5E3BD3A0: 41312020 20202020 20202020 20202020 ?A1 ... 5E3BD7E0: 20202020 20202020 20200300 f8000021 ? .....! OFFSET TABLE: Row - Offset 2 (0x2) - 2103 (0x837) 1 (0x1) - 96 (0x60) 0 (0x0) - 4118 (0x1016) |
其中紅顏色的部分為每行的行頭部分,藍(lán)顏色部分為每行的結(jié)尾部分。
大家可以看到m_type=1即數(shù)據(jù)頁面,大家應(yīng)該很奇怪吧,為什么明明是聚集索引,卻是數(shù)據(jù)頁面呢?正如上面所提到,聚集索引的葉子頁面即數(shù)據(jù)頁面。因?yàn)檫@個(gè)表只有2~3條記錄,所以root頁面還達(dá)不到需要分為B樹的程度,所以該root頁面也是葉子頁面。
我們首先來看一下1000d407的行頭部如何解釋
| 第0位 | 第1-3位 | 第4位 | 第5位 | 第6-7位 | 1個(gè)字節(jié) | 2個(gè)字節(jié) |
| 0 | 000 | 1 | 0 | 00 | 00 | d407 |
| 10 | 00 | 2004 | ||||
| 始終為0 | 0表示主記錄 | 存在NULL位圖 | 存在變長字段 | 保留 | 狀態(tài)B保留 | 字段長度 |
即該行為不存在變長字段的主記錄,且字段長度為2004個(gè)字節(jié)。
那30 00d407該如何解釋呢?即00001100即存在變長字段的主記錄,我們的testNonUniqueCluster怎么會(huì)存在變長字段呢?
在該非唯一聚集索引表中,我們首先插入記錄B、BBB2記錄,再插入B、BBB1記錄,這個(gè)時(shí)候?qū)τ诜俏ㄒ凰饕绾稳プR(shí)別呢?SQL Server在重復(fù)行的行尾增加了8個(gè)額外的字節(jié),稍后我們?cè)俜治鲂形病?/p>
在testUniqueCluster表中正常的行尾為0200fc,其解釋如下0200表示該表有2個(gè)字段,fc則為1111 1100,即前2個(gè)字段不為空。
而對(duì)于testNonUniqueCluster表正常的行尾應(yīng)為0300 f8,其解釋如下0300表示該表有3個(gè)字段,f8則為1111 1000,即前3個(gè)字段不為空;很顯然SQL Server把非唯一索引的標(biāo)識(shí)符也當(dāng)做字段了;但的的確確因?yàn)锽、BBB2和A、AAA1在插入的時(shí)候是唯一的,所以不需要這個(gè)字段。
我們接下來看看B、BBB1行的尾部03 00f8 0100 df070100 0000,0300f8解釋同上,0100即1表示該表一共有1個(gè)變長字段,df07即2015變長字段結(jié)束的位置,最后四個(gè)字節(jié)0100 0000為非唯一索引的標(biāo)識(shí)符,換算成10進(jìn)制即1。
從頁面中記錄的順序我們其實(shí)可以看得出來,聚集索引的行的物理順序與行的實(shí)際存儲(chǔ)沒有太大關(guān)系,而是與記錄槽的順序的有關(guān)。
既然我們?cè)僬務(wù)摼奂饕?#xff0c;那就不能不說聚集索引的中間節(jié)點(diǎn)和根節(jié)點(diǎn)了,
為了簡化處理,我們使用testUniqueCluster來做進(jìn)一步的研究。
該表包含2個(gè)定長字段,合計(jì)2000字節(jié),加上相應(yīng)的頭部的4個(gè)管理字節(jié)和尾部的3個(gè)管理字節(jié),共計(jì)2007個(gè)字節(jié),頁頭還需要96個(gè)字節(jié),每行的偏移量需要2個(gè)字節(jié),所以單頁8192字節(jié)只能容納大概4條記錄。也就是說當(dāng)我們完成第五條記錄時(shí)就應(yīng)該產(chǎn)生分頁現(xiàn)象了。
| INSERT INTO testUniqueCluster VALUES('C','CCC1') INSERT INTO testUniqueCluster VALUES('D','DDD1') INSERT INTO testUniqueCluster VALUES('E','EEE1') TRUNCATE TABLE tablepage; INSERT INTO tablepage EXEC ('DBCC IND(testdb,testUniqueCluster,1)'); SELECT b.name table_name, CASE WHEN c.type=0 THEN '堆' WHEN c.type=1 THEN '聚集' WHEN c.type=2 THEN '非聚集' ELSE '其他' END index_type, c.name index_name, PagePID,IAMPID,ObjectID,IndexID,Pagetype,IndexLevel, NextPagePID,PrevPagePID FROM tablepage a,sys.objects b,sys.indexes c WHERE A.ObjectID=b.object_id AND A.ObjectID=c.object_id AND a.IndexID=c.index_id |
以下為該表的詳細(xì)頁面分布
| index_name | PagePID | IAMPID | IndexID | Pagetype | IndexLevel | NextPagePID | PrevPagePID |
| … | 234 | NULL | 1 | 10 | NULL | 0 | 0 |
| … | 233 | 234 | 1 | 1 | 0 | 248 | 0 |
| … | 239 | 234 | 1 | 2 | 1 | 0 | 0 |
| … | 248 | 234 | 1 | 1 | 0 | 0 | 233 |
我們?cè)儆胹ys.system_internals_allocation_units來看一下該表的頁面概要信息。
| name | total_pages | used_pages | data_pages | first_address | root_address | IAM_address |
| testUniqueCluster | 4 | 4 | 2 | 1:233 | 1:239 | 1:234 |
從以上兩個(gè)表格,我們可以看出IAM頁面未發(fā)生變化,仍舊是第234頁面。
根節(jié)點(diǎn)頁面發(fā)生了變化,現(xiàn)在是第239頁面,pagetype=2,即索引頁面,新增加了一個(gè)數(shù)據(jù)頁面第248頁面,第233頁面仍繼續(xù)存在;同時(shí)在第248和233個(gè)頁面之間存在著互鏈的關(guān)系。
同時(shí)觀察一下數(shù)據(jù),發(fā)現(xiàn)在第233頁中存在A、AAA1;B、BBB1;C、CCC1;D、DDD1等4條記錄,而第248頁中則存在E、EEE1記錄,也就是說對(duì)于SQL Server來說索引的分裂應(yīng)該是以最小代價(jià)進(jìn)行,而不是完全均衡策略。
再讓我們用DBCC PAGE(1,testDB,239,3)觀察一下根節(jié)點(diǎn)的內(nèi)容。
| FileId | PageId | Row | Level | ChildFileId | ChildPageId | name (key) | KeyHashValue |
| 1 | 239 | 0 | 1 | 1 | 233 | NULL | (6f4251ce1f81) |
| 1 | 239 | 1 | 1 | 1 | 248 | E | (201c8aeace10) |
因?yàn)檫@是個(gè)索引的非葉子節(jié)點(diǎn),所以連表現(xiàn)形式都簡化了。
FieldId為當(dāng)前頁面的文件ID
PageId為當(dāng)前頁面的頁面ID
Row表示為當(dāng)前的slot槽
Level為1表示為當(dāng)前為非葉子節(jié)點(diǎn)
ChildFieldId表示為插槽號(hào)指向的頁面的文件ID
ChildPageId表示為插槽號(hào)指向的頁面的頁面ID
Name表示為當(dāng)前索引的鍵值
KeyHashValue為SQL Server鍵值的內(nèi)部表示的hash值。
即E右側(cè)的數(shù)據(jù)指向第248頁面,而左側(cè)的則指向第233頁面。
那么再讓我們插入4條記錄看看根頁面的變化。
| INSERT INTO testUniqueCluster VALUES('C','CCC1') INSERT INTO testUniqueCluster VALUES('D','DDD1') INSERT INTO testUniqueCluster VALUES('E','EEE1') DBCC PAGE(1,testDB,239,3) |
| FileId | PageId | Row | Level | ChildFileId | ChildPageId | name (key) | KeyHashValue |
| 1 | 239 | 0 | 1 | 1 | 233 | NULL | (6f4251ce1f81) |
| 1 | 239 | 1 | 1 | 1 | 248 | E | (201c8aeace10) |
| 1 | 239 | 2 | 1 | 1 | 249 | I | (201cbd800c11) |
現(xiàn)在我們可以看到在根節(jié)點(diǎn)上又增加了一個(gè)新的鍵值I,凡是大于等于I的記錄均指向第249頁;結(jié)合前面的描述,我們可以得到下面的索引結(jié)構(gòu)變化示意圖。
總結(jié)
以上是生活随笔為你收集整理的SQL Server2008存储结构之聚集索引的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 黑枸杞泡水喝的禁忌
- 下一篇: 赵明:华为5G产品如果回归值得高兴 行业