说理论,总是枯燥的,先来段搞笑视频,清清脑
模特兒走秀摔倒集錦:http://947kan.com/video/player-53088-0-0.html
原文地址:
http://www.sqlskills.com/BLOGS/PAUL/post/Inside-the-Storage-Engine-Anatomy-of-a-page.aspx
继续存储引擎揭秘系列,今天讨论页结构。页是用来存储记录的。一个页是数据库文件中的一个 8192 字节段。页在数据文件中开始于 0 字节,并按 8192 字节对齐。下面是一个页的基本结构图:
页面分成个部分:BUFFER、PAGEHEADER、DATA、OFFSETTABLE
BUFFER:显示了指定页面的缓冲信息。由于它是一个内存中结构,所以仅当页面处于内存中时候才有效.
PAGEHEADER:显示指定页面的所有报头字段信息。
DATA:显示每行数据的具体存储.
OFFSETTABLE:显示了所有行偏移矩阵的内容.
页头部
页头部大小为
96
字节。在这部分我最想做的事是使用
DBCC PAGE
来看一个页头部,然后解释一下所有的字段含义。我使用以前《
page split
》文章用的数据库,下面是
DBBC PAGE
部分输出:
DBCC
TRACEON (3604)
DBCC
PAGE ('pagesplittest', 1, 143, 1);
GO
m_pageId
= (1:143) m_headerVersion = 1
m_type = 1
m_typeFlagBits = 0x4 m_level =
0
m_flagBits = 0x200
m_objId (AllocUnitId.idObj) = 68 m_indexId
(AllocUnitId.idInd) = 256
Metadata: AllocUnitId = 72057594042384384
Metadata: PartitionId =
72057594038386688 Metadata: IndexId =
1
Metadata: ObjectId = 2073058421 m_prevPage = (0:0) m_nextPage =
(1:154)
pminlen = 8
m_slotCnt = 4 m_freeCnt = 4420
m_freeData = 4681
m_reservedCnt = 0
m_lsn = (18:116:25)
m_xactReserved = 0 m_xdesId = (0:0)
m_ghostRecCnt = 0
m_tornBits = 1333613242
下面是所有字段的解释(注意页中字段并不是按下面顺序存储排列的):
- m_pageId
- 这个字段标明了文件
ID
及页在该文件中的位置。在本例中(
1:143
)
- m_headerVersion
- 页头部版本。自从
7.0
以来,这个值总是为
1
。
- m_type
- 页类型,你可能见到的页类型如下:
- 1 -
数据页。这种页存储堆或聚集索引的叶节点中的数据记录。
- 2 -
索引页。这种页存储聚集索引的非叶节点
或非聚集索引的所有结点
的索引记录
- 3 -
文本混合页。这种文本页存储小段的
LOB
值以及文本树的内部。这种页可被同一索引
/
堆的分区的
LOB
值所共享。
- 4 -
文本树页。这种文本页存储一个单独列的大段的
LOB
值。
- 7 -
排序页。这种页存储在一次排序操作中的中间结果。
- 8 - GAM
页。这种页存有一个
GAM
区间(每个数据文件逻辑上被分割成约
4GB
大小的段,这个“约
4GB
”就是一个页中的位图所能表示的区)中所有区的分配信息:一个区是否已经被分配?
GAM
表示全局分配映射(
G
lobal A
llocation M
ap
)。第一个
GAM
页是每个文件的第
2
页。
- 9 -
SGAM
页。这种页也是存有一个
GAM
区间中所有区的分配情况:一个区是否可以分配混合页?
SGAM
表示共享
GAM
。第一个
SGAM
页是每个文件的第
3
页。
- 10 – IAM
页。这种页包含一个
GAM
区间中哪些区已分配给一个索引(
SQL
SERVER 2000
中)或分配单元(
2005
中)。
IAM
表示索引分配映射(
Index Allocation Map
)。
- 11 - PFS
页。这种页存有一个
PFS
区间(每个数据文件逻辑上被分割成约
64MB
大小的段,这个“约
64MB
”就是一个页中的字节所能表示的页)中每个的页的分配和可用空间的信息。
PFS
表示页的可用空间。第一个
PFS
页是每个文件的第
1
页。
- 13 -
启动页。这种页含有数据库的信息。每个数据库只有一个启动页,它是数据文件
1
的第
9
页。
- 15 -
文件头页。这种页包含文件的信息。每个文件一个文件头页,是文件的第
0
页。
- 16 -
差异映射页
(DIFF)
。这种页包含有自上次完整备份以来一个
GAM
区间中已发生改变的区的信息。第一个
DIFF
页是每个文件的第
6
页。
- 17 - ML
映射页。这种页包含有自上次备份以来一个
GAM
区间中哪些区在
BULK-LOGGED
模式下发生了大容量日志操作。你为了大容量加载或重建索引而将恢复模式变为
BULK-LOGGED
,有了这种页就不用担心打断备份链了。第一个
ML
页是每个文件的第
7
页。
- m_typeFlagBits
- 基本未用。数据页和索引页,此字段总是
4
;其他类型页(除了
PFS
页)该字段总是为
0
。如果一个
PFS
页的
m_typeFlagBits
为
1
,表示
PFS
页映射的
PFS
区间中的至少有一页中有至少一个幽灵记录。
- m_level
- 这表示页在
B
树上的层。
- 叶节点是
0
层,每向上加一层增加
1
,直到根节点(即
B
树的最高点)。
- 在
SQL
SERVER 2000
中,一个聚集索引的叶节点(数据页)是
0
,它的上面一层(索引页)也是
0
,然后才向上增加,直到根节点。所以在
SQL SERVER 2000
中为了判断一个页是否是叶节点,你需要查看
m_type
和
m_level
两个字段。
- 除了索引页外所有其他类型的页,其层次总是为
0
。
- m_flagBits
- 这包含了一些用来描述页的不同的标志。比如,
0x200
表示页上面有校验和(就像我们的例子);
0x100
表示页上面有残损页保护。
- 一些位在
SQL
SERVER 2005
中不再使用。
- m_objId
- m_indexId
- 在
SQL
SERVER 2000
中,这些
ID
表示本页所分配给的实际的关系对象和索引的
ID
。在
SQL SERVER 2005
中,不再是这样了。分配元数据全部改了,所以这些字段不再表示
ID
了而是表示本页所属的分配单元了。
- m_prevPage
- m_nextPage
- 这是
B
树上同一层中的前一页和后一页的指针。这些字段都是
6
个字节的页
ID.
- 索引的每层上的页都用一个双向链表按索引的逻辑顺序(就是定义的索引键)链接起来。因为存在碎片,所以指针指向的页没有必要跟当前页物理上相邻。
- B
树上一层最左面的页的
m_prevpage
为
NULL;
最右面的页的
m_nextpage
为
NULL.
- 堆或者只有一页的索引中,所有页的这两个指针都是
NULL.
- pminlen
- m_slotCnt
- m_freeCnt
- m_freeData
- 从页开始到最后一个
记录结尾的下一字节的偏移值。如果它前面也有可用空间也没有关系。
- m_reservedCnt
- 由活动事务保留的可用空间的字节数。这可以防止用光所有的可用空间,以保证事务能正确回滚。改变这个值有一套复杂的算法。
- m_lsn
- m_xactReserved
- 最后一次加到
m_reservedCnt
上的数目。
- m_xdesId
- 最近一次加到
m_reserverdCnt
上的事务的内部
ID.
- m_ghostRecCnt
- m_tornBits
- 本字段或者是页的校验和或者是残损页保护中被替换的位。这是依赖于本数据库到底是用哪种保护方式。
注意:我并没有包括以
Metadata
开头的字段,因为它们并不是页头部的一部分。在
SQL SERVER 2005
的开发过程中,我花了大量的精力来改写
DBCC PAGE
命令,为了节省每个使用者在系统表中查询对象
/
索引
ID
的时间,我在
DBCC PAGE
中进行了查询,并输出最终的结果。
记录
见我专门的文章。
行偏移数组
有一个很常见的误解是页中的记录是按逻辑顺序存储的,这是错误的。还有一种误解是一个页中所有可用空间总是维护成一块连续的段,这也是错误的。(是的,上面的图片中显示可用空间确实是一个段,这通常是当页是逐步填充时才会发生)
如果从页中删除一个记录,页上剩下的记录并不会立刻被压紧的(
compact
)——如果插入时需要的话会插入过程会花时间压紧的,但删除过程不会进行压紧操作的。
考虑一个完全满的页,这表示当删除发生时,机会造成页中有可用空间洞。如果一个有新记录要插入到页中,而页上的一个洞足够大,那为什么还要压紧呢?直接将记录放进去就行了。如果这个记录需要逻辑上放在所有其他记录的后面,而我们的插入位置却是在中间——这不会坏了事情吗?
不
会的。因为行偏移数组是排序的,并且每次记录插入和删除后都会重排。只要行偏移数组第一个条目指向逻辑上的第一个记录,就不会有事。每个条目是两个字节的
页中偏移——所以操作行偏移数组比操作记录有效多了。只有当我们知道页中有足够的可用空间,但这些空间分散在页内,我们才需要压紧记录让可用空间变成一整
段。
一个有趣的事实是,行偏移数组是从页尾部向前增长的。所以当记录压紧后,可用空间是从新行的顶部到行偏移数组的尾部。
优质内容筛选与推荐>>
1、VC++实现获取进程端口检测木马2、C/C++结构体字节对齐详解3、MySQL Workbench建表时 PK NN UQ BIN UN ZF AI 的含义4、IIS服务器应用程序不可用的解决办法5、LaTeX中的数学公式