区的当前使用情况 | GAM比特位设置 | SGAM比特位设置 |
自由,未使用 | 1 | 0 |
统一类型或已全部使用的混合区 | 0 | 0 |
含有自由页面的混合区 | 0 | 1 |
如果SQL Server需要找到一个新的完全没有使用的区,那么它可以使用任何一个在GAM页面中对应的比特位值为1的区。如果SQL Server需要找到一个有着可用空间(有一个或多个自由页面)的混合类型的区,那么它可以寻找一个对应的GAM中的值为0、SGAM中的值为1的区。如果不存在有可用空间的混合类型的区,SQL Server会使用GAM页面来寻找一个全新的区并将其分配为混合类型的区,然后使用该区中的一页。如果根本没有自由区,那么这个文件已经满了。
第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页 |
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页,即和我们看到的数据库数据文件的大小完全相等。
第8页 | 第8页 | 第8页 | 第N页 | 第173页 | 第279页 |
m_type=1 | m_type=13 | m_type in (1,2,10) | N/A | ||
Data页 | Boot页 | 主要为内部系统表相关信息 | 未分配 |
以下截图是通过SQLServer2008的Internals Viewer插件看到的整体页面结构,该插件是从http://www.SQLInernalsViewer.com网站下载的,分为不同的.net版本。
备注:TESTDB为新创建的空数据库,没有任何用户自定义对象,直到有建表脚本为止;
关于数据库页类型如下所示:
类型 | 页面类型名称 | 页面类型描述 |
1 | Data page | 堆表和聚集索引的叶子节点数据 |
2 | Index page | 聚集索引的非叶子节点和非聚集索引的所有索引记录 |
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 | 排序时所用到的临时页,排序中间操作存储数据用的。 |
8 | GAM page | 全局分配映射(Global Allocation Map,GAM)页面 这些页面记录了哪些区已经被分配并用作何种用途。 |
9 | SGAM page | 共享全局分配映射(Shared Global Allocation Map,GAM)页面 这些页面记录了哪些区当前被用作混合类型的区,并且这些区需含有至少一个未使用的页面。 |
10 | IAM page. | 有关每个分配单元中表或索引所使用的区的信息 |
11 | PFS page. | 有关页分配和页的可用空间的信息 |
13 | boot page. | 记录了关于数据库的信息,仅存于每个数据库的第9页 |
15 | file header page | 记录了关于数据库文件的信息,存于每个数据库文件的第0页 |
16 | DCM page | 记录自从上次全备以来的数据改变的页面,以备差异备份 |
17 | BCM page | 有关每个分配单元中自最后一条 BACKUP LOG 语句之后的大容量操作所修改的区的信息 |
实际上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部分:
显示给定页面的缓冲信息,是内存中的结构,用于管理页面,该信息仅当该页面处于内存时才有意义。关于这个部分我们知之甚少,基本上无法找到相关材料。
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部分显示的是该页面上的所有报头字段的数据
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页面。