热门标签 | HotTags
当前位置:  开发笔记 > 数据库 > 正文

深入解析SQLServer行迁移记录-mysql教程

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

引:本文基于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来看文件是没有意义的,只用内部命令查参数也是没有意义的。

推荐阅读
  • 本文详细探讨了不同SQL数据库管理系统(DBMS)在限制输出结果、拼接字段和日期时间处理方面的函数差异。通过具体示例,帮助读者理解并掌握如何在不同DBMS中实现相同功能。 ... [详细]
  • 本文详细介绍了IBM DB2数据库在大型应用系统中的应用,强调其卓越的可扩展性和多环境支持能力。文章深入分析了DB2在数据利用性、完整性、安全性和恢复性方面的优势,并提供了优化建议以提升其在不同规模应用程序中的表现。 ... [详细]
  • PHP 5.2.5 安装与配置指南
    本文详细介绍了 PHP 5.2.5 的安装和配置步骤,帮助开发者解决常见的环境配置问题,特别是上传图片时遇到的错误。通过本教程,您可以顺利搭建并优化 PHP 运行环境。 ... [详细]
  • 深入理解 SQL 视图、存储过程与事务
    本文详细介绍了SQL中的视图、存储过程和事务的概念及应用。视图为用户提供了一种灵活的数据查询方式,存储过程则封装了复杂的SQL逻辑,而事务确保了数据库操作的完整性和一致性。 ... [详细]
  • 构建基于BERT的中文NL2SQL模型:一个简明的基准
    本文探讨了将自然语言转换为SQL语句(NL2SQL)的任务,这是人工智能领域中一项非常实用的研究方向。文章介绍了笔者在公司举办的首届中文NL2SQL挑战赛中的实践,该比赛提供了金融和通用领域的表格数据,并标注了对应的自然语言与SQL语句对,旨在训练准确的NL2SQL模型。 ... [详细]
  • 本文详细介绍了HTML中标签的使用方法和作用。通过具体示例,解释了如何利用标签为网页中的缩写和简称提供完整解释,并探讨了其在提高可读性和搜索引擎优化方面的优势。 ... [详细]
  • 数据库内核开发入门 | 搭建研发环境的初步指南
    本课程将带你从零开始,逐步掌握数据库内核开发的基础知识和实践技能,重点介绍如何搭建OceanBase的开发环境。 ... [详细]
  • 本文深入探讨 MyBatis 中动态 SQL 的使用方法,包括 if/where、trim 自定义字符串截取规则、choose 分支选择、封装查询和修改条件的 where/set 标签、批量处理的 foreach 标签以及内置参数和 bind 的用法。 ... [详细]
  • 使用C#开发SQL Server存储过程的指南
    本文介绍如何利用C#在SQL Server中创建存储过程,涵盖背景、步骤和应用场景,旨在帮助开发者更好地理解和应用这一技术。 ... [详细]
  • 本文探讨了适用于Spring Boot应用程序的Web版SQL管理工具,这些工具不仅支持H2数据库,还能够处理MySQL和Oracle等主流数据库的表结构修改。 ... [详细]
  • 本文详细介绍了如何通过多种编程语言(如PHP、JSP)实现网站与MySQL数据库的连接,包括创建数据库、表的基本操作,以及数据的读取和写入方法。 ... [详细]
  • 在当前众多持久层框架中,MyBatis(前身为iBatis)凭借其轻量级、易用性和对SQL的直接支持,成为许多开发者的首选。本文将详细探讨MyBatis的核心概念、设计理念及其优势。 ... [详细]
  • 在使用 DataGridView 时,如果在当前单元格中输入内容但光标未移开,点击保存按钮后,输入的内容可能无法保存。只有当光标离开单元格后,才能成功保存数据。本文将探讨如何通过调用 DataGridView 的内置方法解决此问题。 ... [详细]
  • 本文详细介绍了如何在 Linux 平台上安装和配置 PostgreSQL 数据库。通过访问官方资源并遵循特定的操作步骤,用户可以在不同发行版(如 Ubuntu 和 Red Hat)上顺利完成 PostgreSQL 的安装。 ... [详细]
  • 如何在PostgreSQL中查看数据表
    本文将指导您使用pgAdmin工具连接到PostgreSQL数据库,并展示如何浏览和查找其中的数据表。通过简单的步骤,您可以轻松访问所需的表结构和数据。 ... [详细]
author-avatar
手浪用户2602936705
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有