失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > SQL Server 存储结构之GAM SGAM

SQL Server 存储结构之GAM SGAM

时间:2022-04-16 23:05:52

相关推荐

SQL Server 存储结构之GAM SGAM

原文首发于it168专稿,链接见/a/0916/1104/000001104500.shtml

谈到GAM和SGAM,我们不得不从数据库的页和区说起。一个数据库由用户定义的空间构成,这些空间用来永久存储用户对象,例如数据库管理信息、表和索引。这些空间被分配在一个或多个操作系统文件中。

当我们创建一个数据库的时候,例如以缺省的方式CREATE DATABASE TESTDB,SQLServer自动帮我们创建好如下两个数据库文件。

这两个数据文件是实实在在的操作系统文件,其中一个是叫行数据文件,用来存储数据库的各种对象,另外一个是日志文件,从来记录数据变化的过程。

从逻辑角度而言,数据库的最小存储单位为页即8kb。

数据库被分成若干逻辑页面(每个页面8KB),并且在每个文件中,所有页面都被连续地从0到x编号,其中x是由文件的大小决定的。我们可以通过指定一个 数据库ID、一个文件ID、一个页码来引用任何一个数据页。每个数据页则用来存储表和索引,以及相关的数据库管理信息。

我们顺着上面数据文件的路径可以找到该文件,观察一下新建的数据文件的大小为:

2.18 MB (2,293,760 字节)=2,293,760b/8kb=280个页面=35个区

数据库进行空间管理的最小单位为区(extents)。

一个区由8个逻辑上连续的页面组成(64KB的空间)。为了能够更有效地分配空间,SQL Server 不会为少量的数据向数据表分配整区的空间。SQL Server 有两种类型的区。

统一类型的区 这些区为单个对象所有,区中所有的8个数据页只能被所属对象使用。

混合类型的区 这些区能为最多8个对象共享。

SQL Server为新的表或索引从混合类型的区中分配页面。当该表或索引增长到8个页面时,以后所有的分配都使用统一类型的区。

当一张表或一个索引需要更多的空间时,SQL Server需要找到能够用来分配的空间。如果该表或索引整体仍然少于8个页面,SQL Server必须找到能够用来分配的混合类型区构成的空间。如果表或索引有8个页面或更大,SQL Server必须找到一个自由的统一类型的区。

SQL Server使用两种特殊类型的页面来记录哪些区已经被分配出去了,哪些类型(混合类型或统一类型)的区可供使用:

全局分配映射(Global Allocation Map,GAM)页面 这些页面记录了哪些区已经被分配并用作何种用途。一个GAM页面在它所覆盖空间里针对每一个区都有一个数据位。如果数据位为0,那么对应的区正在使用;如 果该数据位为1,那么该区为自由区。一个GAM页面除了页面头部和其他一些需要记入的开销大概有8 000字节或者说64 000位空间可用,所以每个GAM页面可以覆盖64 000个区,也就是大约4GB的数据。这意味着一个文件的每4GB空间对应一个GAM页面。

共享全局分配映射(Shared Global Allocation Map,SGAM)页面 这些页面记录了哪些区当前被用作混合类型的区,并且这些区需含有至少一个未使用的页面。就像一个GAM页面,每一个SGAM页面覆盖了大约64 000个区,也就是大约4GB的数据。一个SGAM页面在它所覆盖空间里针对每一个区都有一个数据位。如果数据位为1,那么对应的被使用的区为混合类型, 并且该区有一些自由页面;如果数据位为0,那么对应的区不是一个混合类型的区,或者虽然是一个混合类型的区,但是所有的页面都已被使用了。

表4-2显示了基于每一个区当前的使用情况,在GAM和SGAM中该区所对应的比特位模式。

如果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)。

在SQLServer的每一个数据库中的前八页顺序都是固定的。

除了第9页为数据库的BOOT页以外,从第8页到第173页为SQLServer内部系统表的相关存储信息,然后从第174页到第279页为未分配页面。因为第一页从0开始,所以刚好280页,即和我们看到的数据库数据文件的大小完全相等。

以下截图是通过SQLServer的Internals Viewer插件看到的整体页面结构,该插件是从网站下载的,分为不同的.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的数据库文件,所以我们执行以下命令即可。

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部分:

显示给定页面的缓冲信息,是内存中的结构,用于管理页面,该信息仅当该页面处于内存时才有意义。关于这个部分我们知之甚少,基本上无法找到相关材料。

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 = 0x00000000bpageno = (1:3)

bdbid = 8breferences = 3 bUse1 = 14428

bstat = 0xc00009 blog = 0x21212159bnext = 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 = 0Metadata: IndexId = 0 Metadata: ObjectId = 99

m_prevPage = (0:0) m_nextPage = (0:0)pminlen = 90

m_slotCnt = 2m_freeCnt = 6 m_freeData = 8182

m_reservedCnt = 0m_lsn = (18:435:5)m_xactReserved = 0

m_xdesId = (0:0) m_ghostRecCnt = 0 m_tornBits = 177043542

Allocation Status

GAM (1:2)=ALLOCATEDSGAM (1:3)=NOT ALLOCATEDPFS(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页面。

如果觉得《SQL Server 存储结构之GAM SGAM》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。