數(shù)據(jù)行所占的最小空間是多少呢? 先來看看下面這張經(jīng)典數(shù)據(jù)行結(jié)構(gòu)圖(引自Inside Sql Server) 因?yàn)榭勺冮L(zhǎng)度類型的列會(huì)有額外的空間開銷,所以不考慮可變長(zhǎng)度的字段。 現(xiàn)在計(jì)算一下除去實(shí)際數(shù)據(jù)后每個(gè)數(shù)據(jù)行所需要占用的空間: 狀態(tài)位A占一個(gè)字節(jié),狀態(tài)位B占
數(shù)據(jù)行所占的最小空間是多少呢?
先來看看下面這張經(jīng)典數(shù)據(jù)行結(jié)構(gòu)圖(引自Inside Sql Server)
因?yàn)榭勺冮L(zhǎng)度類型的列會(huì)有額外的空間開銷,所以不考慮可變長(zhǎng)度的字段。 現(xiàn)在計(jì)算一下除去實(shí)際數(shù)據(jù)后每個(gè)數(shù)據(jù)行所需要占用的空間:
狀態(tài)位A占一個(gè)字節(jié),狀態(tài)位B占1個(gè)字節(jié),用于標(biāo)識(shí)固定字段長(zhǎng)度的占2個(gè)字節(jié),標(biāo)識(shí)固定列數(shù)量的占去2個(gè)字節(jié),NULL位圖至少占用1個(gè)字節(jié)。
這樣算下來:1+1+2+2+1=7。 也就是在不考慮實(shí)際數(shù)據(jù)的情況下一個(gè)數(shù)據(jù)行最少要占用7個(gè)字節(jié)。如果實(shí)際數(shù)據(jù)長(zhǎng)度為1,那么每一行所占用的空間就為8個(gè)字節(jié)。真的是這樣嗎?
現(xiàn)在做個(gè)實(shí)驗(yàn)驗(yàn)證一下,在下文中如果沒有特殊的說明,所有的敘述都是以數(shù)據(jù)表沒有建立聚集索引為前提的。
創(chuàng)建一個(gè)表:
Create Table table1
(
col1 char(1)
)
插入2行數(shù)據(jù):
insertinto table11 values('a')
insertinto table11 values('b')
然后使用DBCC PAGE命令察看表的結(jié)構(gòu)(為了方便察看,我只保留了Data和Offset Table部分)
DATA:
Slot 0, Offset 0x60, Length 9, DumpStyle BYTE
Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP
Memory Dump @0x44EFC060
00000000: 10000500 610100fe 08†††††††††††††††††....a....
Slot 1, Offset 0x69, Length 9, DumpStyle BYTE
Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP
Memory Dump @0x44EFC069
00000000: 10000500 620100fe 08†††††††††††††††††....b....
OFFSET TABLE:
Row - Offset
1 (0x1) - 105 (0x69)
0 (0x0) - 96 (0x60)
然后使用DBCC PAGE命令察看表的結(jié)構(gòu)(為了方便察看,我只保留了Data和Offset Table部分)
DATA:
Slot 0, Offset 0x60, Length 9, DumpStyle BYTE
Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP
Memory Dump @0x4500C060
00000000: 10000600 61610100 fe†††††††††††††††††....aa...
Slot 1, Offset 0x69, Length 9, DumpStyle BYTE
Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP
Memory Dump @0x4500C069
00000000: 10000600 62620100 fe†††††††††††††††††....bb...
OFFSET TABLE:
Row - Offset
1 (0x1) - 105 (0x69)
0 (0x0) - 96 (0x60)
通過上面兩個(gè)實(shí)驗(yàn)可以看出:只有一個(gè)字段,并且類型和長(zhǎng)度為char(1)的表的數(shù)據(jù)行所占用的空間和只有一個(gè)字段,并且類型和長(zhǎng)度為char(2)的表的數(shù)據(jù)行所占用的空間是相等的。為什么會(huì)出現(xiàn)這種情況呢?
實(shí)際上,如果一個(gè)數(shù)據(jù)行的長(zhǎng)度沒有達(dá)到規(guī)定的最小長(zhǎng)度(9 bytes),SQL SERVER會(huì)自動(dòng)在該行的后面填充一個(gè)字節(jié),將長(zhǎng)度擴(kuò)展到9。這就是我們之前看到的那個(gè)08出現(xiàn)的原因。所以SQLSERVER數(shù)據(jù)行最小的長(zhǎng)度為9,而不是8。
您可能要問了:為什么SQL SERVER會(huì)有這樣的規(guī)定呢?
這個(gè)規(guī)定是為了更新操作(update)建立的龜腚。我們知道RID用來標(biāo)識(shí)數(shù)據(jù)頁數(shù)據(jù)行,當(dāng)數(shù)據(jù)頁中的某一行數(shù)據(jù)被更新以至于現(xiàn)有的數(shù)據(jù)頁無法再容納該行時(shí),SQLSERVER會(huì)將該行移動(dòng)到新的數(shù)據(jù)頁中。原有的數(shù)據(jù)行位置并不被其他的數(shù)據(jù)行占用,而是替換成一個(gè)forwarding pointer。該pointer指向那個(gè)被更新的數(shù)據(jù)行的新的位置。而這個(gè)pointer大小為9 byte(header占一byte,RID占8byte,RID的構(gòu)成:4個(gè)字節(jié)的pageID,2個(gè)字節(jié)的fileID,2個(gè)字節(jié)的slotID)。所以sql server為了保證能夠成功地將數(shù)據(jù)行替換成forwarding pointer,規(guī)定每個(gè)數(shù)據(jù)行要最少要占用9個(gè)byte。
您可能又要問了,為什么需要那個(gè)鳥forwarding pointer呢,直接移動(dòng)不就ok了。反正數(shù)據(jù)頁和數(shù)據(jù)頁之間是沒有關(guān)系的,數(shù)據(jù)頁中的數(shù)據(jù)行又是無序的。要那個(gè)forwarding pointer有啥用???
原因是這樣的,當(dāng)數(shù)據(jù)頁中的某一行數(shù)據(jù)被更新以至于現(xiàn)有的數(shù)據(jù)頁無法再容納該行時(shí),SQLSERVER會(huì)將該行移動(dòng)到新的數(shù)據(jù)頁中。之后就出現(xiàn)了兩個(gè)選擇:
1 更新原有數(shù)據(jù)頁中所有數(shù)據(jù)行的RID,這可能是個(gè)非常昂貴的操作(可能有249個(gè)索引)。
2 不更新數(shù)據(jù)頁中的RID,將那個(gè)空缺的位置替換成一個(gè)forwarding pointer。在以后的查詢時(shí)按照這個(gè)pointer查找紀(jì)錄。
您可能還有疑問:如果表建立了聚集索引,每個(gè)數(shù)據(jù)行也要占用9byte嗎
是這樣的。雖然聚集索引的更新操作有所不同,但是為了方便b-tree轉(zhuǎn)換成heap,也需要保持9個(gè)byte
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com