如果SQL Server需要找到一个新的完全没有使用的区,那么它可以使用任何一个在GAM页面中对应的比特位值为1的区。如果SQL Server需要找到一个有着可用空间(有一个或多个自由页面)的混合类型的区,那么它可以寻找一个对应的GAM中的值为0、SGAM中的值为1的区。如果不存在有可用空间的混合类型的区,SQL Server会使用GAM页面来寻找一个全新的区并将其分配为混合类型的区,然后使用该区中的一页。如果根本没有自由区,那么这个文件已经满了。
SQL Server能够迅速地锁定一个文件中的GAM页面,因为它总是位于任何数据库文件的第三页上(页码为2)。SGAM页面是在第四页上(页码为3)。下一个GAM页面出现在第一个GAM页面(页码为2)以后的每511 230个页面中,并且下一个SGAM页面出现在第一个SGAM页面(页码为3)以后的每511 230个页面中。每一个数据库文件的页码为0的页面是文件头页面,并且每个文件仅有一页。页码0是头文件页,页码1是页面自由空间页(Page Free Space,PFS)。
在SQLServer2008的每一个数据库中的前八页顺序都是固定的。
除了第9页为数据库的BOOT页以外,从第8页到第173页为SQLServer2008内部系统表的相关存储信息,然后从第174页到第279页为未分配页面。因为第一页从0开始,所以刚好280页,即和我们看到的数据库数据文件的大小完全相等。
以下截图是通过SQLServer2008的Internals Viewer插件看到的整体页面结构,该插件是从http://www.SQLInernalsViewer.com网站下载的,分为不同的.net版本。
备注:TESTDB为新创建的空数据库,没有任何用户自定义对象,直到有建表脚本为止;
关于数据库页类型如下所示:
实际上SQLServer还包括一些未公开的页面类型,例如type 19,type 14等等。
本章我们主要介绍GAM页和SGAM页,其他页面类型会稍后介绍。
那么如何查看页面信息呢,从SQLServer2000起便开始提供了一个读取数据页结构的命令DBCC Page。该命令为非文档化的命令,具体如下:
DBCC Page ({dbid|dbname},filenum,pagenum[,printopt])
具体参数描述如下:
dbid 包含页面的数据库ID
dbname 包含页面的数据库的名称
filenum 包含页面的文件编号
pagenum 文件内的页面
printopt 可选的输出选项;选用其中一个值:
0:默认值,输出缓冲区的标题和页面标题
1:输出缓冲区的标题、页面标题(分别输出每一行),以及行偏移量表
2:输出缓冲区的标题、页面标题(整体输出页面),以及行偏移量表
3:输出缓冲区的标题、页面标题(分别输出每一行),以及行偏移量表;每一行后跟分别列出的它的列值
如果要想看到这些输出的结果,还需要设置DBCC TRACEON(3604)。
如前文所述,GAM页一定存在于该数据库的第二个页面,SGAM页则一定存在于该数据库的第三个页面;而每一个数据库都会存在文件编号为1的数据库文件,所以我们执行以下命令即可。
让我们首先从GAM页开始看起:
BUFFER部分:
显示给定页面的缓冲信息,是内存中的结构,用于管理页面,该信息仅当该页面处于内存时才有意义。关于这个部分我们知之甚少,基本上无法找到相关材料。
PAGE HEADER部分:
PAGE HEADER部分显示的是该页面上的所有报头字段的数据
PAGE HEADER这部分内容只有通过DBCC PAGE(TESTDB,1,2,2)即整体输出页面才能够展现;通过与上面表格的对照,我们勉强能识别一些相关存储信息;当这部分缺乏官方文档的支持,为了避免无谓的猜测,所以暂时就不做深入探讨了。
DATA 部分
DATA部分一般分为若干插槽号(Slot),如果是数据页或索引页的话,可以理解为一行记录,SQLServer通过文件号+页面号+插槽号用来唯一标识表中的每一条记录。但在GAM页中我们可以把Slot 0理解为GAM页的保留页,共计94个字节。
从第194个字节开始(页面总是从第0个字节开始的),到第196个字节,这三个字节代表已分配的分区的情况。即0000C0。
我们再来看一下DBCC PAGE(TESTDB,1,2,3)的执行结果。
上面显示从第1页到第168页已分配,而第176页到272页未分配,和DBCC PAGE(TESTDB,1,2,2)显示的194个页面似乎有些矛盾,实际上是不矛盾的。如前文所述,GAM对未使用的分区标识为0,而对已分配的分区标识为1
1个分区=64页,因为前128个页面均已分配,所以前两个字节为00 00
从第128个页面起到第175个页面也均已分配,实际上为6个区为0也就是说连续6个bit为0,一个字节为8个bit,最后两个bit为11,所以该字节为0000 0011,在此需要反转一下相关二进制位;反转之后为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页的全貌吧。
总结一下,关于GAM和SGAM页比较困难的地方:
1、 关于GAM和SGAM页中的BUFFER信息基本无法理解,也找不到相关材料。
2、 PAGE HEADER的部分信息和Slot 0中的一部分信息,也无法找到相关材料。
3、 SGAM页中的NOT ALLOCATED实际上是统一类型区或者已使用完的混合类型的区,而ALLOCATED实际上为含有自由页面的混合区。
4、 GAM页中0代表已分配,1代表自由区;和一般的标志位的含义刚好相反。
5、 GAM和SGAM实际上只分配了280个页面,即35个区;显示出来的数据内容虽然很多,但后面的分区信息实际上是不存在的。
6、 GAM和SGAM通过DBCC的printopt为3的属性显示出来的页面分配信息看似是断号的。
7、 GAM和SGAM的区信息的字节是通过二级制反转得到的。
GAM和SGAM页的总的大小为8192个字节;文件头为96个字节,slot 0为94个字节,slot 1的头部的系统信息为4个字节,尾部的系统信息为10个字节,所以有效存储应为7988个字节,63904个区,511230个页;事实上当数据文件超过约4G的时候,我们将能在第511232页、 第511233页分别找到其对应的GAM、SGAM页面。
新闻热点
疑难解答