[转帖]真TM长的:SQL Server 2008存储结构——GAM和SGAM、PFS结构、IAM结构、DCMBCM
談到GAM和SGAM,我們不得不從數(shù)據(jù)庫的頁和區(qū)說起。
?https://blog.csdn.net/snowfoxmonitor/article/details/49991015
一個數(shù)據(jù)庫由用戶定義的空間構(gòu)成,這些空間用來永久存儲用戶對象,例如數(shù)據(jù)庫管理信息、表和索引。這些空間被分配在一個或多個操作系統(tǒng)文件中。
當我們創(chuàng)建一個數(shù)據(jù)庫的時候,例如以缺省的方式CREATE DATABASE TESTDB,SQLServer自動幫我們創(chuàng)建好如下兩個數(shù)據(jù)庫文件。
這兩個數(shù)據(jù)文件是實實在在的操作系統(tǒng)文件,其中一個是叫行數(shù)據(jù)文件,用來存儲數(shù)據(jù)庫的各種對象,另外一個是日志文件,從來記錄數(shù)據(jù)變化的過程。
從邏輯角度而言,數(shù)據(jù)庫的最小存儲單位為頁即8kb。
數(shù)據(jù)庫被分成若干邏輯頁面(每個頁面8KB),并且在每個文件中,所有頁面都被連續(xù)地從0到x編號,其中x是由文件的大小決定的。我們可以通過指定一個數(shù)據(jù)庫ID、一個文件ID、一個頁碼來引用任何一個數(shù)據(jù)頁。每個數(shù)據(jù)頁則用來存儲表和索引,以及相關的數(shù)據(jù)庫管理信息。
我們順著上面數(shù)據(jù)文件的路徑可以找到該文件,觀察一下新建的數(shù)據(jù)文件的大小為:
2.18 MB(2,293,760 字節(jié))=2,293,760b/8kb=280個頁面=35個區(qū)
數(shù)據(jù)庫進行空間管理的最小單位為區(qū)(extents)。
一個區(qū)由8個邏輯上連續(xù)的頁面組成(64KB的空間)。為了能夠更有效地分配空間,SQL Server 2008不會為少量的數(shù)據(jù)向數(shù)據(jù)表分配整區(qū)的空間。SQL Server 2008有兩種類型的區(qū)。
統(tǒng)一類型的區(qū)? 這些區(qū)為單個對象所有,區(qū)中所有的8個數(shù)據(jù)頁只能被所屬對象使用。
混合類型的區(qū)?這些區(qū)能為最多8個對象共享。
SQL Server為新的表或索引從混合類型的區(qū)中分配頁面。當該表或索引增長到8個頁面時,以后所有的分配都使用統(tǒng)一類型的區(qū)。
當一張表或一個索引需要更多的空間時,SQL Server需要找到能夠用來分配的空間。如果該表或索引整體仍然少于8個頁面,SQL Server必須找到能夠用來分配的混合類型區(qū)構(gòu)成的空間。如果表或索引有8個頁面或更大,SQL Server必須找到一個自由的統(tǒng)一類型的區(qū)。
SQL Server使用兩種特殊類型的頁面來記錄哪些區(qū)已經(jīng)被分配出去了,哪些類型(混合類型或統(tǒng)一類型)的區(qū)可供使用:
全局分配映射(Global Allocation Map,GAM)頁面? 這些頁面記錄了哪些區(qū)已經(jīng)被分配并用作何種用途。一個GAM頁面在它所覆蓋空間里針對每一個區(qū)都有一個數(shù)據(jù)位。如果數(shù)據(jù)位為0,那么對應的區(qū)正在使用;如果該數(shù)據(jù)位為1,那么該區(qū)為自由區(qū)。一個GAM頁面除了頁面頭部和其他一些需要記入的開銷大概有8000字節(jié)或者說64 000位空間可用,所以每個GAM頁面可以覆蓋64 000個區(qū),也就是大約4GB的數(shù)據(jù)。這意味著一個文件的每4GB空間對應一個GAM頁面。
共享全局分配映射(Shared Global Allocation Map,SGAM)頁面? 這些頁面記錄了哪些區(qū)當前被用作混合類型的區(qū),并且這些區(qū)需含有至少一個未使用的頁面。就像一個GAM頁面,每一個SGAM頁面覆蓋了大約64 000個區(qū),也就是大約4GB的數(shù)據(jù)。一個SGAM頁面在它所覆蓋空間里針對每一個區(qū)都有一個數(shù)據(jù)位。如果數(shù)據(jù)位為1,那么對應的被使用的區(qū)為混合類型,并且該區(qū)有一些自由頁面;如果數(shù)據(jù)位為0,那么對應的區(qū)不是一個混合類型的區(qū),或者雖然是一個混合類型的區(qū),但是所有的頁面都已被使用了。
表4-2顯示了基于每一個區(qū)當前的使用情況,在GAM和SGAM中該區(qū)所對應的比特位模式。
| 區(qū)的當前使用情況 | GAM比特位設置 | SGAM比特位設置 |
| 自由,未使用 | 1 | 0 |
| 統(tǒng)一類型或已全部使用的混合區(qū) | 0 | 0 |
| 含有自由頁面的混合區(qū) | 0 | 1 |
如果SQL Server需要找到一個新的完全沒有使用的區(qū),那么它可以使用任何一個在GAM頁面中對應的比特位值為1的區(qū)。如果SQL Server需要找到一個有著可用空間(有一個或多個自由頁面)的混合類型的區(qū),那么它可以尋找一個對應的GAM中的值為0、SGAM中的值為1的區(qū)。如果不存在有可用空間的混合類型的區(qū),SQL Server會使用GAM頁面來尋找一個全新的區(qū)并將其分配為混合類型的區(qū),然后使用該區(qū)中的一頁。如果根本沒有自由區(qū),那么這個文件已經(jīng)滿了。
SQL Server能夠迅速地鎖定一個文件中的GAM頁面,因為它總是位于任何數(shù)據(jù)庫文件的第三頁上(頁碼為2)。SGAM頁面是在第四頁上(頁碼為3)。下一個GAM頁面出現(xiàn)在第一個GAM頁面(頁碼為2)以后的每511 230個頁面中,并且下一個SGAM頁面出現(xiàn)在第一個SGAM頁面(頁碼為3)以后的每511 230個頁面中。每一個數(shù)據(jù)庫文件的頁碼為0的頁面是文件頭頁面,并且每個文件僅有一頁。頁碼0是頭文件頁,頁碼1是頁面自由空間頁(Page Free Space,PFS)。
在SQLServer2008的每一個數(shù)據(jù)庫中的前八頁順序都是固定的。
| 第0頁 | 第1頁 | 第2頁 | 第3頁 | 第4頁 | 第5頁 | 第6頁 | 第7頁 |
| m_type=15 | m_type=11 | m_type=8 | m_type=9 | m_type=0 | m_type=0 | m_type=16 | m_type=17 |
| 頭文件頁 | PFS頁 | GAM頁 | SGAM頁 | 保留頁 | 保留頁 | DCM頁 | BCM頁 |
除了第9頁為數(shù)據(jù)庫的BOOT頁以外,從第8頁到第173頁為SQLServer2008內(nèi)部系統(tǒng)表的相關存儲信息,然后從第174頁到第279頁為未分配頁面。因為第一頁從0開始,所以剛好280頁,即和我們看到的數(shù)據(jù)庫數(shù)據(jù)文件的大小完全相等。
| 第8頁 | 第9頁 | 第10頁 | 第N頁 | 第173頁 | 第279頁 |
| m_type=1 | m_type=13 | m_type in (1,2,10) | N/A | ||
| Data頁 | Boot頁 | 主要為內(nèi)部系統(tǒng)表相關信息 | 未分配 | ||
?
???????? 以下截圖是通過SQLServer2008的InternalsViewer插件看到的整體頁面結(jié)構(gòu),該插件是從http://www.SQLInernalsViewer.com網(wǎng)站下載的,分為不同的.net版本。
???????? 備注:TESTDB為新創(chuàng)建的空數(shù)據(jù)庫,沒有任何用戶自定義對象,直到有建表腳本為止;
?
關于數(shù)據(jù)庫頁類型如下所示:
| 類型 | 頁面類型名稱 | 頁面類型描述 |
| 1 | Data page | 堆表和聚集索引的葉子節(jié)點數(shù)據(jù) |
| 2 | Index page | 聚集索引的非葉子節(jié)點和非聚集索引的所有索引記錄 |
| 3 | Text mixed page | A text page that holds small chunks of LOB values plus internal parts of text tree. These can be shared between LOB values in the same partition of an index or heap. |
| 4 | Text tree page | A text page that holds large chunks of LOB values from a single column value. |
| 7 | Sort page | 排序時所用到的臨時頁,排序中間操作存儲數(shù)據(jù)用的。 |
| 8 | GAM page | 全局分配映射(Global Allocation Map,GAM)頁面? 這些頁面記錄了哪些區(qū)已經(jīng)被分配并用作何種用途。 |
| 9 | SGAM page | 共享全局分配映射(Shared Global Allocation Map,GAM)頁面? 這些頁面記錄了哪些區(qū)當前被用作混合類型的區(qū),并且這些區(qū)需含有至少一個未使用的頁面。 |
| 10 | IAM page. | 有關每個分配單元中表或索引所使用的區(qū)的信息 |
| 11 | PFS page. | 有關頁分配和頁的可用空間的信息 |
| 13 | boot page. | 記錄了關于數(shù)據(jù)庫的信息,僅存于每個數(shù)據(jù)庫的第9頁 |
| 15 | file header page. | 記錄了關于數(shù)據(jù)庫文件的信息,存于每個數(shù)據(jù)庫文件的第0頁 |
| 16 | DCM page | 記錄自從上次全備以來的數(shù)據(jù)改變的頁面,以備差異備份 |
| 17 | BCM page. | 有關每個分配單元中自最后一條 BACKUP LOG 語句之后的大容量操作所修改的區(qū)的信息 |
實際上SQLServer還包括一些未公開的頁面類型,例如type19,type 14等等。
本章我們主要介紹GAM頁和SGAM頁,其他頁面類型會稍后介紹。
那么如何查看頁面信息呢,從SQLServer2000起便開始提供了一個讀取數(shù)據(jù)頁結(jié)構(gòu)的命令DBCC Page。該命令為非文檔化的命令,具體如下:
DBCC Page({dbid|dbname},filenum,pagenum[,printopt])
具體參數(shù)描述如下:
dbid?????? 包含頁面的數(shù)據(jù)庫ID
dbname??? ?包含頁面的數(shù)據(jù)庫的名稱
filenum??? 包含頁面的文件編號
pagenum?? ?文件內(nèi)的頁面
printopt?? 可選的輸出選項;選用其中一個值:
?????????? 0:默認值,輸出緩沖區(qū)的標題和頁面標題
?????????? 1:輸出緩沖區(qū)的標題、頁面標題(分別輸出每一行),以及行偏移量表
?????????? 2:輸出緩沖區(qū)的標題、頁面標題(整體輸出頁面),以及行偏移量表
? ?????????3:輸出緩沖區(qū)的標題、頁面標題(分別輸出每一行),以及行偏移量表;每一行后跟分別列出的它的列值
如果要想看到這些輸出的結(jié)果,還需要設置DBCC TRACEON(3604)。
如前文所述,GAM頁一定存在于該數(shù)據(jù)庫的第二個頁面,SGAM頁則一定存在于該數(shù)據(jù)庫的第三個頁面;而每一個數(shù)據(jù)庫都會存在文件編號為1的數(shù)據(jù)庫文件,所以我們執(zhí)行以下命令即可。
| DBCC TRACEON(3604) DBCC PAGE(TESTDB,1,2,1)? —查看GAM頁信息 DBCC PAGE(TESTDB,1,3,1)? —查看SGAM頁信息 DBCC PAGE(TESTDB,1,2,2)? —查看GAM頁信息和整體輸出頁面 DBCC PAGE(TESTDB,1,3,2)? —查看SGAM頁信息和整體輸出頁面 DBCC PAGE(TESTDB,1,2,3)? —查看GAM頁信息及相應列值 DBCC PAGE(TESTDB,1,3,3)? —查看SGAM頁信息及相應列值 DBCC PAGE(TESTDB,1,2,1) WITH TABLERESULTS? —以表格形式查看SGAM頁信息及相應列值 DBCC PAGE(TESTDB,1,3,1) WITH TABLERESULTS? —以表格形式查看SGAM頁信息及相應列值 |
我們可以看到一個完整的頁面分為四個部分;BUFFER、PAGE HEADER、DATA和OFFSET TABLE。
讓我們首先從GAM頁開始看起:
BUFFER部分:
顯示給定頁面的緩沖信息,是內(nèi)存中的結(jié)構(gòu),用于管理頁面,該信息僅當該頁面處于內(nèi)存時才有意義。關于這個部分我們知之甚少,基本上無法找到相關材料。
| BUF @0x03585CD8 | 每一次清空緩存再次查詢,地址都會改變 |
| bpage = 0x060B4000 | 每一次清空緩存再次查詢,地址都會改變 |
| bhash = 0x00000000 | 相對不變 |
| bpageno = (1:2) | 當前頁面地址 |
| bdbid = 8 | sys.databases.database_id |
| breferences = 1 | 每一次清空緩存再次查詢,地址都會改變 |
| bUse1 = 41490 | 每一次清空緩存再次查詢,地址都會改變 |
| bstat = 0xc00009 | 相對不變 |
| blog = 0x59ca2159 | 相對不變 |
| bnext = 0x00000000 | 相對不變 |
?
PAGE HEADER部分:
PAGE HEADER部分顯示的是該頁面上的所有報頭字段的數(shù)據(jù)
| Page @0x060B4000????????????????? | 同BUFFER中的bpage地址 |
| m_pageId = (1:2)????????????????? | 數(shù)據(jù)頁號 |
| m_headerVersion = 1????????????? | 頭文件版本號,一直為1 |
| m_type = 8?????????????????????????? | 頁面類型,8為GAM頁面 |
| m_typeFlagBits = 0x0????????????? | 數(shù)據(jù)頁和索引頁為4,其他頁為0 |
| m_level = 0????????????????????? | 該頁在索引頁(B樹)中的級數(shù) |
| m_flagBits = 0x200????????????????????? | 頁面標志 |
| m_objId (AllocUnitId.idObj) = 99? | 同Metadata: ObjectId |
| m_indexId (AllocUnitId.idInd) = 0 | 同Metadata: IndexId |
| Metadata: AllocUnitId = 6488064 | 存儲單元的ID,sys.allocation_units.allocation_unit_id |
| Metadata: PartitionId = 0???????? | 數(shù)據(jù)頁所在的分區(qū)號,sys.partitions.partition_id |
| Metadata: IndexId = 0??????????? | 頁面的索引號,sys.objects.object_id&sys.indexes.index_id |
| Metadata: ObjectId = 99???????????????? | 該頁面所屬的對象的id,sys.objects.object_id |
| m_prevPage = (0:0)??????????????? | 該數(shù)據(jù)頁的前一頁面;主要用在數(shù)據(jù)頁、索引頁和IAM頁 |
| m_nextPage = (0:0)?????????????? | 該數(shù)據(jù)頁的后一頁面;主要用在數(shù)據(jù)頁、索引頁和IAM頁 |
| pminlen = 90??????????????????????????? | 定長數(shù)據(jù)所占的字節(jié)數(shù) |
| m_slotCnt = 2??????? ????????????? | 頁面中的數(shù)據(jù)的行數(shù) |
| m_freeCnt = 6??????????????????? | 頁面中剩余的空間 |
| m_freeData = 8182?????????????????????? | 從第一個字節(jié)到最后一個字節(jié)的空間字節(jié)數(shù) |
| m_reservedCnt = 0???????????????? | 活動事務釋放的字節(jié)數(shù) |
| m_lsn = (15:216:82)????????????? | 日志記錄號 |
| m_xactReserved = 0?????????????????? ??? | 最新加入到m_reservedCnt領域的字節(jié)數(shù) |
| m_xdesId = (0:0)????????????????? | 添加到m_reservedCnt的最近的事務id |
| m_ghostRecCnt = 0??????????????? | 幻影數(shù)據(jù)的行數(shù) |
| m_tornBits = 177369273????????????????? | 頁的校驗位或者被由數(shù)據(jù)庫頁面保護形式?jīng)Q定分頁保護位取代 |
| Allocation Status???????????????? |
|
| GAM (1:2) = ALLOCATED???????????? | 在GAM頁上的分配情況 |
| SGAM (1:3) = NOT ALLOCATED?????? | 在SGAM頁上的分配情況 |
| PFS (1:1) = 0x44 ALLOCATED 100_PCT_FULL | 在PFS頁上的分配情況,該頁為96%~100%滿, |
| DIFF (1:6) = CHANGED????????????? |
|
| ML (1:7) = NOT MIN_LOGGED??????? |
|
PAGE HEADER這部分內(nèi)容只有通過DBCC PAGE(TESTDB,1,2,2)即整體輸出頁面才能夠展現(xiàn);通過與上面表格的對照,我們勉強能識別一些相關存儲信息;當這部分缺乏官方文檔的支持,為了避免無謂的猜測,所以暫時就不做深入探討了。
| 5E32C000:?? 01080000 00020000 00000000 00005a00 ?..............Z.???????? |
| 5E32C010:?? 00000000 00000200 63000000 0600f61f ?........c.......???????? |
| 5E32C020:?? 02000000 01000000 0f000000 d8000000 ?................???????? |
| 5E32C030:?? 52000000 00000000 00000000 b970920a ?R............p..???????? |
| 5E32C040:?? 00000000 00000000 00000000 00000000 ?................???????? |
| 5E32C050:?? 00000000 00000000 00000000 00000000 ?................ |
?
DATA 部分
| Slot 0, Offset 0x60, Length 94, DumpStyle BYTE |
| Record Type = PRIMARY_RECORD???????? Record Attributes =????????????????? |
| Memory Dump @0x5E2AC060 |
| 00000000:?? 00005e00 00000000 00000000 00000000 ?..^.............???????? |
| 00000010:?? 00000000 00000000 00000000 00000000 ?................???????? |
| 00000020:?? 00000000 00000000 00000000 00000000 ?................???????? |
| 00000030:?? 00000000 00000000 00000000 00000000 ?................???????? |
| 00000040:?? 00000000 00000000 00000000 00000000 ?................???????? |
| 00000050:?? 00000000 00000000 00000000 0000??????..............?????????? |
|
|
| Slot 1, Offset 0xbe, Length 7992, DumpStyle BYTE |
| Record Type = PRIMARY_RECORD???????? Record Attributes =????????????????? |
| Memory Dump @0x5E2AC0BE |
| 00000000:?? 0000381f?0000c0ff ffffffff ffffffff ?..8.............???????? |
| 00000010:?? ffffffff ffffffff ffffffff ffffffff ?................???????? |
DATA部分一般分為若干插槽號(Slot),如果是數(shù)據(jù)頁或索引頁的話,可以理解為一行記錄,SQLServer通過文件號+頁面號+插槽號用來唯一標識表中的每一條記錄。但在GAM頁中我們可以把Slot 0理解為GAM頁的保留頁,共計94個字節(jié)。
從第194個字節(jié)開始(頁面總是從第0個字節(jié)開始的),到第196個字節(jié),這三個字節(jié)代表已分配的分區(qū)的情況。即0000C0。
我們再來看一下DBCC PAGE(TESTDB,1,2,3)的執(zhí)行結(jié)果。
| GAM: Header @0x5E2AC064 Slot 0, Offset 96 |
| status = 0x0???????????????????????? |
| GAM: Extent Alloc Status @0x5E2AC0C2 |
| (1:0)??????? - (1:168)????? =???? ALLOCATED?????????????????????????????? |
| (1:176)????? - (1:272)????? = NOT ALLOCATED |
上面顯示從第1頁到第168頁已分配,而第176頁到272頁未分配,和DBCC PAGE(TESTDB,1,2,2)顯示的194個頁面似乎有些矛盾,實際上是不矛盾的。如前文所述,GAM對未使用的分區(qū)標識為0,而對已分配的分區(qū)標識為1
1個分區(qū)=64頁,因為前128個頁面均已分配,所以前兩個字節(jié)為00 00
從第128個頁面起到第175個頁面也均已分配,實際上為6個區(qū)為0也就是說連續(xù)6個bit為0,一個字節(jié)為8個bit,最后兩個bit為11,所以該字節(jié)為0000 0011,在此需要反轉(zhuǎn)一下相關二進制位;反轉(zhuǎn)之后為1100 0000即為C0。
最后讓我們用Internals Viewer插件看一下GAM頁的全貌吧。
?
SGAM頁面
| PAGE: (1:3) ? BUFFER: BUF @0x0358A7F4 bpage = 0x062AE000???????????? bhash = 0x00000000????????????? bpageno = (1:3) bdbid = 8????????????????????? breferences = 3???????????????? bUse1 = 14428 bstat = 0xc00009?????????????? blog = 0x21212159?????????????? bnext = 0x00000000 ?????????????????????????????????????????????????????????????? PAGE HEADER:?????????????????????????????????????????????????? Page @0x062AE000?????????????????????????????? ???????????????? m_pageId = (1:3)?????????????? m_headerVersion = 1???????????? m_type = 9 m_typeFlagBits = 0x0?????????? m_level = 0???????????????????? m_flagBits = 0x200 m_objId (AllocUnitId.idObj)=99 m_indexId (AllocUnitId.idInd)=0 Metadata: AllocUnitId=6488064 Metadata: PartitionId = 0????? Metadata: IndexId = 0?????????? Metadata: ObjectId = 99 m_prevPage = (0:0)???????????? m_nextPage = (0:0)????????????? pminlen = 90 m_slotCnt = 2????????????????? m_freeCnt = 6?????????????????? m_freeData = 8182 m_reservedCnt = 0????????????? m_lsn = (18:435:5)????????????? m_xactReserved = 0 m_xdesId = (0:0)?????????????? m_ghostRecCnt = 0?????????????? m_tornBits = 177043542 Allocation Status????????????? GAM (1:2)=ALLOCATED??????????? SGAM (1:3)=NOT ALLOCATED?? ???????PFS(1:1)=0x44 ALLOCATED 100_PCT_FULL DIFF (1:6) = CHANGED?????????? ML (1:7) = NOT MIN_LOGGED???????? ? DATA: Slot 0, Offset 0x60, Length 94, DumpStyle BYTE Record Type = PRIMARY_RECORD???????? Record Attributes =????????????????? Memory Dump @0x4F32C060 00000000:?? 00005e00 00000000 00000000 00000000 ?..^.............???????? 00000010:?? 00000000 00000000 00000000 00000000 ?................???????? 00000020:?? 00000000 00000000 00000000 00000000 ?................???????? 00000030:?? 00000000 00000000 00000000 00000000 ?................???????? 00000040:?? 00000000 00000000 00000000 00000000 ?................???????? 00000050:?? 00000000 00000000 00000000 0000??????..............?????????? ? Slot 1, Offset 0xbe, Length 7992, DumpStyle BYTE Record Type = PRIMARY_RECORD???????? Record Attributes =????????????????? Memory Dump @0x4F32C0BE 00000000:?? 0000381f 20ee2000 00000000 00000000 ?..8. . .........???????? 00000010:?? 00000000 00000000 00000000 00000000 ?................ 00001F30:?? 00000000 00000000 ???????????????????........?????????? |
???????? 以下為DBCC PAGE(TESTDB,1,3,3)得到的相關信息,有興趣的可以和20ee20做一下對比。
| (1:0)??????? - (1:32)?????? = NOT ALLOCATED?????????????????????????????? (1:40)?????? -????????????? =???? ALLOCATED?????????????????????????????? (1:48)?????? - (1:64)?????? = NOT ALLOCATED?????????????????????????????? (1:72)?????? - (1:88)?????? =???? ALLOCATED?????????????????????????????? (1:96)?????? -????????????? = NOT ALLOCATED?????????????????????????????? (1:104)????? - (1:120)????? =?? ??ALLOCATED?????????????????????????????? (1:128)????? - (1:160)????? = NOT ALLOCATED?????????????????????????????? (1:168)????? -????????????? =???? ALLOCATED?????????????????????????????? (1:176)????? - (1:272)????? = NOT ALLOCATED |
最后讓我們用Internals Viewer插件看一下SGAM頁的全貌吧。
總結(jié)一下,關于GAM和SGAM頁比較困難的地方:
1、? 關于GAM和SGAM頁中的BUFFER信息基本無法理解,也找不到相關材料。
2、? PAGE HEADER的部分信息和Slot 0中的一部分信息,也無法找到相關材料。
3、? SGAM頁中的NOT ALLOCATED實際上是統(tǒng)一類型區(qū)或者已使用完的混合類型的區(qū),而ALLOCATED實際上為含有自由頁面的混合區(qū)。
4、? GAM頁中0代表已分配,1代表自由區(qū);和一般的標志位的含義剛好相反。
5、? GAM和SGAM實際上只分配了280個頁面,即35個區(qū);顯示出來的數(shù)據(jù)內(nèi)容雖然很多,但后面的分區(qū)信息實際上是不存在的。
6、? GAM和SGAM通過DBCC的printopt為3的屬性顯示出來的頁面分配信息看似是斷號的。
7、? GAM和SGAM的區(qū)信息的字節(jié)是通過二級制反轉(zhuǎn)得到的。
GAM和SGAM頁的總的大小為8192個字節(jié);文件頭為96個字節(jié),slot 0為94個字節(jié),slot 1的頭部的系統(tǒng)信息為4個字節(jié),尾部的系統(tǒng)信息為10個字節(jié),所以有效存儲應為7988個字節(jié),63904個區(qū),511230個頁;事實上當數(shù)據(jù)文件超過約4G的時候,我們將能在第511232頁、 第511233頁分別找到其對應的GAM、SGAM頁面。
SQL Server 2008連載之存儲結(jié)構(gòu)——PFS結(jié)構(gòu)
PFS(Page Free Space),也叫頁面自由空間,該頁面用來跟蹤一個文件中每一個特定的頁面的利用率情況。一個文件中第二個頁面(頁碼1)就是PFS頁面,該頁面的每個字節(jié)都記錄了相應頁面的分配情況、頁面類型、是否IAM頁、是否包含刪除記錄、以及空間利用率信息;PFS能夠管理和跟蹤8088個頁面的使用情況,即接近64M的空間,以后每8088個頁面將再出現(xiàn)一次。
讓我們首先了解一下PFS的頁面管理字節(jié)的構(gòu)造,管理單位為字節(jié),每字節(jié)管理一個頁面。
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|
| 頁面是否分配 | 是否混合頁面 | 是否IAM頁面 | 是否幻影頁面 | 空間利用率 | ||
第0個bit為保留字節(jié),始終為0
第1個bit表示該頁面是否已分配,我們知道GAM頁用來管理區(qū)是否已分配,但一個區(qū)包含8個頁面,所以用該bit用來準確定位該區(qū)的某個頁面是否已分配出去了。
第2個bit表示該頁面是否混合分區(qū)的一個頁面。
第3個bit表示該頁面是否是一個IAM頁面。
第4個bit表示該頁面中是否包含幻影或已刪除記錄,這有助于SQL Server定期清理幻影或已刪除記錄。
第5~7個頁面表示該頁面的空間使用率情況。
l? 0:表示該頁面為空
l? 1:表示該頁面已使用1~50%
l? 2:表示該頁面已使用51~80%
l? 3:表示該頁面已使用81~95%
l? 4:表示該頁面已使用96~100%
我們可以用dbcc page(testdb,1,1,2)來看一下PFS的頁面結(jié)構(gòu),BUFFER和PAGE HEADER再次就不做詳述了,PFS關于頁面分配的信息是從第100個字節(jié)開始的,最后四個字節(jié)為系統(tǒng)保留字節(jié),總計管理8088頁。
其中頭四頁均為44,換算成2進制即0100 0100,即為未分配(大概為保留頁的緣故),且為已分配完成的混合區(qū)或統(tǒng)一類型區(qū),非IAM頁,且無幻影記錄,空間利用率96~100%。
| 4F09C060:?? 00009c1f 44444444 00004444 60647060 ?....DDDD..DD`dp`???????? |
| 4F09C070:?? 74706070 60606060 60707060 40404040 ?tp`p`````pp`@@@@???????? |
| 4F09C080:?? 40404040 61706070 60606070 60306060 ?@@@@ap`p```p`0``???????? |
| 4F09C090:?? 60217024 60706060 60606060 40203020 ?`!p$`p``````@ 0????????? |
| 4F09C0A0:?? 20202820 60606060 60606070 60606060 ?? ( ```````p````???????? |
| 4F09C0B0:?? 70203020 30706070 70607060 70203060 ?p 0 0p`pp`p`p 0`???????? |
| 4F09C0C0:?? 70203068 70607060 70607060 70607060 ?p 0hp`p`p`p`p`p`???????? |
| 4F09C0D0:?? 70203060 60602020 60702030 20306070 ?p 0```? `p 0 0`p???????? |
| 4F09C0E0:?? 60702830 60707070 60606070 60706070 ?`p(0`ppp```p`p`p????? ??? |
| 4F09C0F0:?? 60706070 40404020 20202020 60706070 ?`p`p@@@???? `p`p???????? |
| 4F09C100:?? 60706060 64616070 60706070 60706070 ?`p``da`p`p`p`p`p???????? |
| 4F09C110:?? 60700000 00000000 00000000 00000000 ?`p..............???????? |
| 4F09C120:?? 00000000 00000000 00000000 00000000 ?................???????? ? |
| 4F09DFF0:?? 00000000 00000000 00000000 00006000 ?..............`.???????? |
最后讓我們用Internals Viewer插件看一下PFS頁的全貌吧。
第七個頁面(頁碼6)被稱為差異變更(DifferentialChanged Map,DCM)頁面。它跟蹤一個文件中的哪一個區(qū)在最新一次完全數(shù)據(jù)庫備份以后被修改過。SQL Server用在增量備份時只對已發(fā)生數(shù)據(jù)變更的分區(qū)進行增量備份即可。
第八個頁面(頁碼7)被稱為批量更改映射(Bulk ChangedMap,BCM)頁面,該頁面當文件中的一個區(qū)在最小量或批量記日志操作中被使用時用到。就像GAM和SGAM頁面,DCM和BCM頁面針對它們代表的文件區(qū)間中每一個區(qū)都有一個比特位相對應。這些頁面的常規(guī)間距為511 230個頁面。
此外關于數(shù)據(jù)庫在進行DML操作如何尋找合適的分區(qū)和頁面對數(shù)據(jù)進行處理還是留待后續(xù)介紹吧。
SQL Server 2008連載之存儲結(jié)構(gòu)——IAM結(jié)構(gòu)
索引分配映射(Index Allocation Map,IAM)頁面在4 GB的區(qū)間中跟蹤被一個分配單元所使用的區(qū)。一個分配單元就是一組頁面,這些頁面屬于一個數(shù)據(jù)表或索引的單個分區(qū)。它由下面三種類型頁面中的一種組成:含有常規(guī)的行內(nèi)數(shù)據(jù)的頁面、含有大型對象(Large Object,LOB)數(shù)據(jù)的頁面和含有行溢出數(shù)據(jù)的頁面。其實SQL Server的數(shù)據(jù)頁面類型與Oracle的段的概念有些類似,一個對象包含若干段,而一個段只能屬于一個對象。
假如一張在四個分區(qū)上的含有所有三種類型的數(shù)據(jù)(行內(nèi)數(shù)據(jù)、LOB數(shù)據(jù)和行溢出數(shù)據(jù))的表將會有至少12個IAM頁面。單張IAM頁面也是僅僅覆蓋單個文件的4GB區(qū)間,所以如果分區(qū)跨越多個文件,那么就會有多個IAM頁面,同時如果文件大小超過4GB,并且分區(qū)使用了一個4 GB區(qū)間以外的數(shù)據(jù)頁,那么也將會有額外的IAM數(shù)據(jù)頁。
一個IAM數(shù)據(jù)頁包含一個頁頭(IAM頁頭),該頁頭包含有8個頁面指針槽,還有一組比特位用來將一個范圍內(nèi)的區(qū)映射到一個文件,這個文件并不必一定就是IAM頁面所在的那個文件。頁頭包含有在IAM映射范圍內(nèi)的第一個區(qū)的地址。8個頁面指針槽可能包含指向某些屬于相關對象頁面的指針,這些對象被包含在混合類型的區(qū)中,對一個對象來說,只有第一個IAM頁面含有這些指針的值。一旦一個對象占用的頁面超過8個,它所有的區(qū)都會是統(tǒng)一類型的區(qū)——這意味著一個對象決不會需要超過8個指針來指向處于混合類型區(qū)中的頁面。如果一張表中的數(shù)據(jù)行已被刪除,該表實際上可以使用的指針數(shù)不到8個。比特位映射中的每一個比特位代表了該范圍內(nèi)的一個區(qū),而不論該區(qū)是否被分配給了擁有該IAM的對象。如果一個比特位是打開的,那么在此范圍內(nèi)相關的區(qū)就是被分配給擁有
IAM的對象的;如果一個比特位是關閉的,那么此范圍內(nèi)相關的區(qū)沒有被分配給擁有該IAM的對象。
IAM頁面在需要的時候被分配給每一個對象,并且位于數(shù)據(jù)庫中的隨機位置。每一個IAM頁面覆蓋的可能范圍大約是512 000個頁面。
看概念總歸是比較枯燥的,我們可以構(gòu)建一個具體的例子。
在構(gòu)建例子之前我們首先需要創(chuàng)建一個把地址轉(zhuǎn)換為具體頁碼的函數(shù)。
| CREATE FUNCTION [dbo].f_get_page(@page_num BINARY(6)) RETURNS VARCHAR(11) AS BEGIN ?RETURN(CONVERT(VARCHAR(2),(CONVERT(INT,SUBSTRING(@page_num,6,1))*POWER(2,8))+ ??????? (CONVERT(INT,SUBSTRING(@page_num,5,1))))+':'+ ??????? CONVERT(VARCHAR(11), ??????? (CONVERT(INT,SUBSTRING(@page_num,4,1))*POWER(2,24))+ ??????? (CONVERT(INT,SUBSTRING(@page_num,3,1))*POWER(2,16))+ ???? ???(CONVERT(INT,SUBSTRING(@page_num,2,1))*POWER(2,8))+ ??????? (CONVERT(INT,SUBSTRING(@page_num,1,1))))) END --根據(jù)master.sys.objects構(gòu)建一張叫testIAM的數(shù)據(jù)表 SELECT * INTO testIAM FROM master.sys.objects??? --然后我們根據(jù)之前所知曉的信息,獲取testIAM對象的IAM地址,并根據(jù)f_get_page函數(shù)將地址轉(zhuǎn)換為相應的頁面 SELECT total_pages,used_pages,data_pages, ?????? first_page,root_page,first_iam_page, ?????? 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 ?WHERE container_id IN (SELECT partition_id FROM sys.partitions ???????????????????????? WHERE object_id in (SELECT object_id? FROM sys.objects ????????????????????????????????????????????? WHERE name IN ('testIAM'))) dbcc page(testdb,1,80,3) |
從dbcc page(testdb,1,80,3)可以得到以下信息
| IAM: Header @0x5E20C064 Slot 0, Offset 96 sequenceNumber = 0? status = 0x0??????? objectId = 0 indexId = 0???????? page_count = 0????? start_pg = (1:0) ? IAM: Single Page Allocations @0x5E20C08E Slot 0 = (1:77)???? Slot 1 = (1:89)???? Slot 2 = (0:0) Slot 3 = (0:0)????? Slot 4 = (0:0)????? Slot 5 = (0:0) Slot 6 = (0:0)????? Slot 7 = (0:0)????? IAM: Extent Alloc Status Slot 1 @0x5E20C0C2 (1:0)??????? - (1:272)????? = NOT ALLOCATED??? ??????????????????????????? |
???????? 因為master.sys.objects的對象只有49條數(shù)據(jù),所以只分配了2個頁面,根據(jù)前文所述,分配8個頁面(包括8)以內(nèi)的對象,SQL Server將以把該對象的數(shù)據(jù)分配到混合類型的分區(qū)上,如上SQL Server給予testIAM表只分配了第一個文件的第77和第89個頁面,而并沒有分配同一類型的分區(qū)的頁面。
???????? 假如我們master.sys.objects的數(shù)據(jù)反復插入testIAM表,讓我們觀察一下相應的頁面分配情況。
| declare @maxtime int set @maxtime=0 while @maxtime<20 begin insert into testiam select * from sys.objects set @maxtime=@maxtime+1 end select * from testiam --我們首先還是運行以下system_internals_allcation_units系統(tǒng)表 SELECT total_pages,used_pages,data_pages, ?????? first_page,root_page,first_iam_page, ?????? 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 ?WHERE container_id IN (SELECT partition_id FROM sys.partitions ???????????????????????? WHERE object_id in (SELECT object_id? FROM sys.objects ????????????????????????????????????????????? WHERE name IN ('testIAM'))) --通過上面的結(jié)果,我們可以觀察到這次SQL Server共分配了17個頁面,其中使用了15個頁面,而數(shù)據(jù)頁面只有14個,這是為什么呢? --接著我們再次運行dbcc page命令 dbcc page(testdb,1,80,3) |
結(jié)果如下
| IAM: Single Page Allocations @0x4F1FC08E Slot 0 = (1:77)???? Slot 1 = (1:89)??? Slot 2 = (1:45) Slot 3 = (1:78)???? Slot 4 = (1:90)??? Slot 5 = (1:109) Slot 6 = (1:114)? ??Slot 7 = (1:120)?? IAM: Extent Alloc Status Slot 1 @0x4F1FC0C2 (1:0)??????? - (1:168)????? = NOT ALLOCATED?????????????????????????????? (1:176)????? -????????????? =???? ALLOCATED?????????????????????????????? (1:184)????? - (1:272)????? = NOT ALLOCATED? |
從上述我們可知,slot 0到slot 7一共分配了8個混合類型區(qū)的頁面,由于已經(jīng)超過8頁,所以SQL Server再次分配空間時,就會把同一類型的區(qū)分配給該對象,一個區(qū)包括8個頁面,所以SQL Server為testIAM表共分配了16個頁面,數(shù)據(jù)頁面14個,已使用的頁面除了數(shù)據(jù)頁面還包括該表的一個IAM管理頁面。
還是通過Internals Viewer插件讓我們看一下IAM頁的情況吧
???????? 比較有意思的是SQL Server 2008為什么不從一開始就為每一個對象分配同一類型的頁面,僅僅是為了節(jié)約空間?還是為了與之前版本的兼容性?就不得而知了。
?
SQL Server 2008連載之存儲結(jié)構(gòu)——DCM&BCM
如前所述頁碼6被稱為差異變更(DifferentialChanged Map,DCM)頁面。它跟蹤一個文件中的哪一個區(qū)在最新一次完全數(shù)據(jù)庫備份以后被修改過。這樣SQL Server用在增量備份時只對已發(fā)生數(shù)據(jù)變更的分區(qū)進行增量備份即可。
那么首先讓我們執(zhí)行一下dbcc page(testDB,1,6,2)命令,可以看出前96字節(jié)為文件頭,接下來的96個字節(jié)為保留頁面,從第195個字節(jié)才開始記錄區(qū)是否已做變更。由于是新庫,數(shù)據(jù)對象并不多;ffff 7f,這三個字節(jié)記錄了需要進行下次備份需要進行增量備份的信息。
| DATA: |
| Memory Dump @0x4F1FC000 |
| 4F1FC000:?? 01100000 00000000 00000000 00005a00 ?..............Z.???????? |
| 4F1FC010:?? 00000000 00000200 63000000 0600f61f ?........c.......???????? |
| 4F1FC020:?? 06000000 01000000 16000000 ce000000 ?................???????? |
| 4F1FC030:?? 2a000000 00000000 00000000 997410aa ?*............t..???????? |
| 4F1FC040:?? 01000000 00000000 00000000 00000000 ?................???????? |
| 4F1FC050:?? 00000000 00000000 00000000 00000000 ?................???????? |
| 4F1FC060:?? 00005e00 00000000 00000000 00000000 ?..^.............???????? |
| 4F1FC070:?? 00000000 00000000 00000000 00000000 ?................???????? |
| 4F1FC080:?? 00000000 00000000 00000000 00000000 ?................???????? |
| 4F1FC090:?? 00000000 00000000 00000000 00000000 ?................???????? |
| 4F1FC0A0:?? 00000000 00000000 00000000 00000000 ?................???????? |
| 4F1FC0B0:?? 00000000 00000000 00000000 00000000 ?................???????? |
| 4F1FC0C0:?? 381fffff 7f000000 00000000 00000000 ?8...............???????? |
| 4F1FC0D0:?? 00000000 00000000 00000000 00000000 ?................???????? |
| 4F1FC1B0:?? 00000000 00000000 00000000 00004000 ?..............@.???????? |
| 4F1FC3B0:?? 00000000 00000000 04000000 00000000 ?................???????? |
| 4F1FC5B0:?? 00400000 00000000 00000000 00000000 ?.@..............? |
???????? 讓我們換個視圖來看一下,即執(zhí)行dbcc page(testDB,1,6,3),這樣可以清楚地看到只有第0頁到第183頁是CHANGED狀態(tài),下次備份需要備份這些頁面。
| DIFF_MAP: Header @0x4F1FC064 Slot 0, Offset 96 status = 0x0????????? ??????????????? DIFF_MAP: Extent Alloc Status @0x4F1FC0C2 (1:0)??????? - (1:176)????? =???? CHANGED???????????????????????????????? (1:184)????? - (1:272)????? = NOT CHANGED??? |
接下來當我們執(zhí)行一次testDB庫全備后,再次用dbccpage(testDB,1,6,3)觀察一下變化。
| DIFF_MAP: Extent Alloc Status @0x5E52C0C2 (1:0)??????? - (1:16)?????? =???? CHANGED???????????????????????????????? (1:24)?????? - (1:56)?????? = NOT CHANGED???????????????????????????????? (1:64)?????? -????????????? =???? CHANGED???????????????????????????????? (1:72)??? ???-????????????? = NOT CHANGED???????????????????????????????? (1:80)?????? -????????????? =???? CHANGED???????????????????????????????? (1:88)?????? - (1:272)????? = NOT CHANGED??? |
就會發(fā)現(xiàn)除了一下系統(tǒng)保留頁面,基本上都變更為NOT CHANGED狀態(tài),記住DCM頁面記錄的是區(qū)變更信息,并且系統(tǒng)保留頁面是一定要備份的。
?
???????? BCM頁
頁碼7被稱為批量更改映射(Bulk ChangedMap,BCM)頁面,只有在數(shù)據(jù)庫處于BULK_LOGGED模式,并且沒有執(zhí)行任何bulk批量操作時,才被使用到,因為BULK_LOGGED模式時數(shù)據(jù)庫日志記錄了包含數(shù)據(jù)庫所有改變的完整順序記錄,所以我們能夠?qū)?shù)據(jù)庫還原到任一時間點。
大容量日志恢復模式是一種特殊用途的恢復模式,只應偶爾用于提高某些大規(guī)模大容量操作(如大量數(shù)據(jù)的大容量導入)的性能
與完整恢復模式(完全記錄所有事務)相比,大容量日志恢復模式只對大容量操作進行最小記錄(盡管會完全記錄其他事務)。大容量日志恢復模式保護大容量操作不受媒體故障的危害,提供最佳性能并占用最小日志空間。
但是,大容量日志恢復模式會增加這些大容量復制操作丟失數(shù)據(jù)的風險,因為大容量日志操作阻止再次捕獲對每個事務逐一所做的更改。如果日志備份包含大容量日志操作,則無法還原到該日志備份中的時點,而只能還原整個日志備份。
為跟蹤數(shù)據(jù)頁,日志備份操作依賴于位圖頁的大容量更改,位圖頁針對每個區(qū)包含一位。對于自上次日志備份后由大容量日志操作所更新的每個區(qū),在位圖中將每個位都設置為 1。
因為BCM頁的應用場景比較單一,在此不對BCM頁做相關詳述。
轉(zhuǎn)載于:https://www.cnblogs.com/jinanxiaolaohu/p/9561677.html
總結(jié)
以上是生活随笔為你收集整理的[转帖]真TM长的:SQL Server 2008存储结构——GAM和SGAM、PFS结构、IAM结构、DCMBCM的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: django F和Q 关键字使用
- 下一篇: 【刷题】BZOJ 4176 Lucas的