Home  >  Article  >  Database  >  深入解析SQLServer行迁移记录

深入解析SQLServer行迁移记录

WBOY
WBOYOriginal
2016-06-07 14:55:361109browse

引:本文基于SQL Server数据库mdf数据文件的存储结构,描述的是数据库中一些跨页记录的存储方法。本文为北亚数据恢复中心内部研究成果,目前仅发布于51CTO,转载时请与作者联系。 SQL Server数据库文件的结构定义中,所有数据均以8192字节作为页大小进行记录

引:本文基于SQL Server数据库mdf数据文件的存储结构,描述的是数据库中一些跨页记录的存储方法。本文为北亚数据恢复中心内部研究成果,目前仅发布于51CTO,转载时请与作者联系。

 

SQL Server数据库文件的结构定义中,所有数据均以8192字节作为页大小进行记录存储,当某条数据记录总长未超过8060字节时,变长和定长列存储在同一页/记录中的记录称为行内数据记录。这是最常见,因为一般的记录长度很少会超过8060字节,这种记录的结构相对简单,格式也相对固定。

行内记录会包含一些常见数据类型的列,如intchar(n)varchar(n)datetime2realmoney等。这些类型的数据有变长的也有定长的,所以在更新变长列的数据时,会使记录长度增大,如果增加到超出空闲空间容量时,这样即使此记录小于8060字节,也会造成此记录在这一页中存放不下。这种情况下一条记录就会变化为两条特殊格式的行内记录:forwarded记录和forwarding记录,这两条记录都是小于8060字节的行内记录。这种动作就称为行迁移,这类记录就称为行迁移记录。

 

1.      一般行内记录

一般的行内记录存储格式就是上一节中讲的一般记录格式。记录头部的标志一般是0x30,0x10。

这种记录中的列信息起始偏移是和页头中的pminlen值相同的。记录结构都是标准的一般结构,如下图:

 wKioL1SB0wOzS31oAABafHl7IY8212.jpg



这类记录中的列信息没有异常的偏移数据,也没有异常的列数据.Dbcc page 中称为PRIMARY_RECORD,这种叫法会更准确,不过随便啦,习惯成自然。

 

2.      行迁移记录

行迁移记包含forwarding_stub forwarded 两种类型。这两类记录是因为数据页不能容纳更新后的记录而产生的,不是因为记录长度超过8060B引起的,所以这两类记录依然是行内记录,与行溢出记录是不同的。

这两种记录总是成对出现的,当出现forwarding_stub时,在别的页面中一定有与之对应的一条forwarded记录。

forwarding记录也叫forwarding_stub记录,记录头标志为0x04,它是一条完整的数据记录。虽然结构看起来特殊,但还是符合一般记录的结构,记录中主要内容就是在行中用8字节保存了指向移出后的记录的位置指针。

    forwarded 记录,有时直接叫做迁移记录。记录头部标志为0x32. 它也是一条完整记录,格式跟一般记录略有差异,大体结构还是相同的。这里是存储了溢出前的那条记录的真实数据。

    这两种记录的具体存储结构比较简单和直观,其逻辑结构会在下面的实例列举中结合具体数据记录来分析说明。

 

下面通过实例来讲解一下这两种记录的存储格式:

1.      创建测试环境:

droptablet1

createtablet1(aint,bvarchar(4000),cvarchar(4000))

insertintot1values(1,REPLICATE('b',2000),REPLICATE('c',2000))

insertintot1values(2,REPLICATE('e',2000),REPLICATE('f',2000))

select*fromt1

wKioL1SB01OjCNHcAACAVla0Y0Y195.jpg

 

dbccind(tt,t1,-1)

wKiom1SB0teyPWtBAACUVPDUUHg237.jpg

 

可看出这两条数据记录是存放在一个页面(第78页)里的,可通过dbccpage(tt,1,78,3) winhex来查看这一页面的内容。

 

dbcctraceon(3604)

dbccpage(tt,1,78,3)

结果集中,关注以下信息:

m_slotCnt = 2          m_freeCnt = 58         m_freeData = 8130   

 

Slot 0 Offset 0x60 Length4017

Record Type =PRIMARY_RECORD         Record Attributes=  NULL_BITMAP VARIABLE_COLUMNS

Record Size = 4017

 

Slot 1 Offset 0x1011Length 4017

Record Type =PRIMARY_RECORD         Record Attributes=  NULL_BITMAP VARIABLE_COLUMNS

Record Size = 4017

    明显看出在78页面中有两条PRIMARY_RECORD记录,长度都是4017字节。

    winhex中打开此数据库文件tt.mdf,跳转到78页面,可以直观地看到数据记录为2条,在数据区可以直接看到记录中的数据。通过页尾的行偏移,可以分别定位到这两条记录。Winhex中此页面数据如下图所示:

 

wKioL1SB04HSfv8PAAIqOfxb45o140.jpg

通过这两种方式都可以看到这两条记录都是存储在此页面的PRIMARY_RECORD记录,总共占用8034字节,没有超过8060

 

2.      更新数据记录,产生迁移记录

updatet1setb=REPLICATE('b',3000)wherea=1;

注意:修改的是第一条记录。

dbccind(tt,t1,-1)

wKiom1SB0wOSzVVTAACmtSY0pvo633.jpg

从结果集看出数据记录存储在7880两页中。下面我们到这两个页面里去查看记录的详细情况。

l        FORWARDING记录

dbccpage(tt,1,78,3)

m_slotCnt = 2        m_freeCnt = 4066     m_freeData = 8139

 

Slot 0 Offset 0x1fc2Length 9

Record Type = FORWARDING_STUB        RecordAttributes =                  Record Size= 9

Memory Dump@0x000000000D30BFC2

0000000000000000:   04500000 0001000000.P.......       

Forwarding to  =  file1 page 80 slot 0                                  

 

Slot 1 Offset 0x1011Length 4017

Record Type =PRIMARY_RECORD         Record Attributes=  NULL_BITMAP VARIABLE_COLUMNS

Record Size = 4017                 

Memory Dump@0x000000000D30B011

0000000000000000:   30000800 02000000 03000002 00e107b10...............

0000000000000010:   0f656565 65656565 65656565 65656565.eeeeeeeeeeeeeee

0000000000000020:   65656565 65656565 65656565 65656565eeeeeeeeeeeeeeee

0000000000000030:   65656565 65656565 65656565 65656565eeeeeeeeeeeeeeee

……

解释一下结果集中的参数:

m_slotCnt = 2 :数据页面中有两条记录。

m_freeCnt = 4066 :数据页中的空闲空间为4066字节,第一条记录所占用的空间已经被释放,说明原来的这条记录数据已被移出此页。

m_freeData = 8139 :自由空间起始位置变大了,说明自由空间起始后移了,这是因为原来的第一条记录被移动到第二条后边了。

Slot 0 Offset0x1fc2 Length 9 :第一条记录起始偏移为8130,长度为9.

Slot 1 Offset0x1011 Length 4017 :第二条记录起始偏移为4113,长度为4017,

看这些数字,4113+4017= Slot 0 Offset=8130, 8130+9 = m_freeDat=8139。原来的第一条记录现在还是第一条(slot 0),但是现在存放在了第二条记录之后,并且长度只有9字节。

这条短记录的格式就是FORWARDING_STUB,格式结构有些不同。具体介绍如下:

04是记录头,根据前面介绍的记录头的位图含义可知此记录为无变长列,null位图,无行版本的FORWARDING_STUB记录。后边紧跟着的8个字节是rowid指针,是指向forwarded记录的指针。这里说的rowid,只是一个叫法,不是说它是真实存在的结构体,在上层有时也会遇到这种叫法,其实它只是个虚拟的称谓,用来表示一个指针结构。这8个字节记录了文件号、页号、slot号。这里的rowid实际的存储体是FORWARDING_STUB,是4字节的页号加2字节的文件号再加2字节的slot号。

wKioL1SB06iQwn77AAAqQuRq3y0683.jpg

此条记录:0450000000010000 00,很容易就得出 rowid为(1:80:0),这也对应结果集中的Forwarding to  =  file 1 page 80 slot 0 ,只不过前者是自己解析,后者是sql server 引擎解析。

这是在winhex里的解析展示,会更清晰的验证上面的结构。

wKiom1SB0zSzVn-HAAGCwb08UGo313.jpg

 

l        Forwarded记录

dbccpage(tt,1,80,3)

m_slotCnt = 1                      m_freeCnt = 3065             m_freeData = 5125

 

Slot 0 Offset 0x60 Length5029

Record Type = FORWARDED_RECORD       RecordAttributes =  NULL_BITMAPVARIABLE_COLUMNS

Record Size = 5029                  

Memory Dump@0x000000000D30A060

0000000000000000:   3200080001000000 03000003 00cb0b9b 2...............

0000000000000010:   13a59362 62626262 62626262 62626262...bbbbbbbbbbbbb

0000000000000020:   62626262 62626262 62626262 62626262bbbbbbbbbbbbbbbb

0000000000000030:   62626262 62626262 62626262 62626262bbbbbbbbbbbbbbbb

……

    在第80页中,有一条记录slot 0,起始为96字节,长度为5029,记录类型为 forwarded_record.这条记录就是forwarding_stub中指向的溢出记录,记录着第78页中slot0数据记录的真实数据。

    这是一条完整记录,和一般记录格式略有差异,最大的不同是在记录中加入了一列变长数据。这一变长列加在记录的最后,只是存储引擎使用,对上层是透明的。同时记录的列信息也发生了变化。

    列信息里的变长列数加1,而总列数不发生变化。变长列偏移数组里多出一项,用来指定加入的数据列的结束偏移,这个变长列的长度其实是固定的,都是10字节,应该是考虑到存取性能才把这个数据放在了记录的最后。这列数据其实不是一列标准的列数据,应该是一个forwarding_stub的结构体数据。里边也是存储了一个rowid指针,用8字节保存了指向移出前的记录的位置指针。

    这个加入的列数据的定位解析还有点特殊。它在列信息中的列数据结束偏移是非常规的,是按溢出类型格式存储的。它的值会大于8192字节,其实是真实数值加了0x8000

    现在来具体解释下上面的这条forwarded记录:

    此记录头部状态A的值为0x32,解释为有可变长列,有null位图的forwarded 移出记录。记录总长度为5029字节数。列信息数据为03000003 00cb0b9b13a593。根据列信息结构来解析可得,总列数为3,是包含abc三个字段。Null位图都为0,表示没有为空的列。变长列数也为3,这里是包含了变长列bc和增加列。后边的结束偏移数组有3个,占用6字节,分别表示这3个变长列数据的结束位置。前两个很容易理解,第三个是0x93A5,转为十进制为37797,这明显是不正常的,真实的偏移值应为此值减去0x8000,即为0x13A5,转十进制为5029。这个值在前面的记录总长度处见过,不是巧合。

     这样就可以手工的取出每一个字段值了。取出最后一列的值,会发现它特殊一点:

0004 4e00000001000000 。数据长度为10字节,跟forwarding很相似,只是在头部多加了一个字节的00。字段里的rowid记录了原来的forwarding记录的地址,这里是(1:78:0,这个地址指针正是指向前面我们看到的那条forwarding记录。

下面通过一幅图来总结下此节讲解的forwardingforwarded记录的整体结构。

 

wKioL1SB09azl6l0AACxlLKixC0997.jpg

Forwarding记录和forwarded 记录的相互连接关系

下图是在winhex中的解析展示:

wKiom1SB01nhUGhoAAHEchmqOwg428.jpg

 

    再复杂一点的,如果forwarded记录又有更新,此页面又存放不下时,它还需要进行行迁移,把真实数据记录移到别的页面里存储。这时不会在这一页的记录中加入forwarding_stub去指向新的forwarded记录,而是直接的修改原始数据页中的forwarding记录的迁移指针,使其直接指向最终的forwarded记录。最终的forwarded里存储原始的forwarding的地址指针。这样就不用使用一个指针链,要通过多个指针依次的指向才到达最终数据记录。

    在这章节的讲解中用dbcc page比较多些,使用winhex多是用来做验证。因为dbccpage 是通过引擎自己解释和显示的,逻辑信息会更多,更详细,结构也更清晰,方便用来分析和理论。Winhex中想解析这些信息都是要根据逻辑结构的理解,然后去取得对应对象的数据。

    其实在winhex上观察内部结构,视野会更宽,自由度更大,可以获得所有的信息,包括很多dbcc page结果集中没有的,对整体结构的理解会更好。最主要的dbcc page sql server 引擎给出的这些结构信息,winhex中是自己动手解析的。

    这些逻辑结构的获得,都是通过dbcc pagewinhex结合使用来分析总结出的,只用winhex来看文件是没有意义的,只用内部命令查参数也是没有意义的。

 


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn