咱们都很清楚SQL Server用8KB 的页来存储数据,而且在SQL Server里磁盘 I/O 操做在页级执行。也就是说,SQL Server 读取或写入全部数据页。页有不一样的类型,像数据页,GAM,SGAM等。在这文章里,让咱们一块儿来理解下数据页结构。html
SQL Server把数据记录存在数据页(Data Page)里。数据记录是堆表里、汇集索引里叶子节点的行。sql
数据页由3个部分组成。页头(标头),数据区(数据行和可用空间)及行偏移数组。数据库
在咱们讨论在SQL Server里,数据页内部结构具体是什么样以前,咱们来建立一个表并插入一些记录。数组
1 USE [InternalStorageFormat] 2 GO 3 4 IF EXISTS ( SELECT * 5 FROM sysobjects 6 WHERE id = OBJECT_ID(N'[dbo].[Customers]') 7 AND OBJECTPROPERTY(id, N'IsUserTable') = 1 ) 8 DROP TABLE dbo.Customers 9 10 CREATE TABLE Customers 11 ( 12 FirstName CHAR(50) NOT NULL, 13 LastName CHAR(50) NOT NULL, 14 Address CHAR(100) NOT NULL, 15 ZipCode CHAR(5) NOT NULL, 16 Rating INT NOT NULL, 17 ModifiedDate DATETIME NOT NULL, 18 ) 19 GO 20 21 22 INSERT INTO dbo.Customers 23 ( FirstName , 24 LastName , 25 Address , 26 ZipCode , 27 Rating , 28 ModifiedDate 29 ) 30 VALUES ( 'Woody' , -- FirstName - char(50) 31 'Tu' , -- LastName - char(50) 32 'ZUOQIAO YOUXI TOWN LINHAI CITY' , -- Address - char(50) 33 '0000' , -- ZipCode - char(5) 34 1 , -- Rating - int 35 '2015-05-07 10:09:51' -- ModifiedDate - datetime 36 ) 37 go 2
如今咱们要找出SQL Server给这个表分配的页有哪些,这个就要用到非文档的命令DBCC IND。
它的语法以下:sqlserver
DBCC IND 命令用于查询一个存储对象的内部存储结构信息,该命令有4个参数, 前3个参数必须指定。语法以下:
DBCC IND ( { 'dbname' | dbid }, { 'objname' | objid },{ nonclustered indid | 1 | 0 | -1 | -2 } [, partition_number] )
第一个参数是数据库名或数据库ID。
第二个参数是数据库中的对象名或对象ID,对象能够是表或者索引视图。
第三个参数是一个非汇集索引ID或者 1, 0, 1, or 2. 值的含义:
0: 只显示对象的in-row data页和 in-row IAM 页。
1: 显示对象的所有页, 包含IAM 页, in-row数据页, LOB 数据页row-overflow 数据页 . 若是请求的对象含有汇集因此则索引页也包括。
-1: 显示所有IAM页,数据页, 索引页 也包括 LOB 和row-overflow 数据页。
-2: 显示所有IAM页。
Nonclustered index ID:显示索引的所有 IAM页, data页和索引页,包含LOB和 row-overflow数据页。
为了兼容sql server 2000,第四个参数是可选的,该参数用于指定一个分区号.若是不给定值或者给定0, 则显示所有分区数据。
和DBCC PAGE不一样的是, SQL Server运行DBCC IND不须要开启3604跟踪标志.spa
咱们来执行下列的命令:3d
1 DBCC IND('InternalStorageFormat','Customers',-1)
SQL Server会给咱们以下的输出结果:指针
能够看到有2条记录,一条记录为页面类型(PageType)为10的页和一条记录为页面类型(PageType)为1的页。页面类型(PageType)10是IAM页,页面类型(PageType)1是数据页,它的页ID是79.日志
关于数据库页类型以下所示:code
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 语句以后的大容量操做所修改的区的信息
如今咱们来看看79号类型为1的数据页里存放的数据,这个就要用到DBCC PAGE命令,它的语法以下:
dbcc page 命令读取数据页结构的命令DBCC Page。
该命令为非文档化的命令,具体以下:
DBCC Page ({dbid|dbname},filenum,pagenum[,printopt])
具体参数描述以下:
dbid 包含页面的数据库ID
dbname 包含页面的数据库的名称
filenum 包含页面的文件编号
pagenum 文件内的页面
printopt 可选的输出选项;选用其中一个值:
0:默认值,输出缓冲区的标题和页面标题
1:输出缓冲区的标题、页面标题(分别输出每一行),以及行偏移量表
2:输出缓冲区的标题、页面标题(总体输出页面),以及行偏移量表
3:输出缓冲区的标题、页面标题(分别输出每一行),以及行偏移量表;每一行
后跟分别列出的它的列值
要想看到这些输出的结果,还须要设置DBCC TRACEON(3604)。
咱们来执行下列的命令:
1 DBCC TRACEON(3604) 2 DBCC PAGE(InternalStorageFormat,1,79,3) 3 GO
SQL Server会给咱们包含4个部分的输出。第1部分是BUFFER,里面是一些内存分配信息,对此咱们没多少兴趣。下一部分是固定96 bytes大小的页头(page header),页头(page header)会相似以下显示:
页头相关字段的含义:
再来看下页面相关分配状况:
接下来就是用于存放实际数据的槽(slot),每条记录存放一个槽(slot)里。0号槽在页里拥有第1条数据,1号槽拥有第2条数据,以此类推。经过下面的图片,你能够看到咱们记录大小是224 bytes,217 bytes(50+50+100+5+4+8) 的定长和7 bytes 的系统行开销。
页的最后一部分是行偏移数组表,咱们能够用参数为1的DBCC PAGE命令来,在输出信息的底部得到。
执行以下的命令:
1 DBCC TRACEON(3604) 2 DBCC PAGE(InternalStorageFormat,1,79,3) 3 GO
SQL Server在输出信息的底部,给咱们以下的信息:
这个行偏移表,应该从下往上读。每条槽条目是一个2 bytes长的指针指向页里槽偏移量。这里咱们插入了2条记录,因此表里有2个槽条目。第1条记录指向第96 bytes,恰好在页头后。这个行偏移表能够帮助咱们管理页面的记录。在页里的行偏移表里,每条记录须要2 bytes的大小来存储。于此相似,在堆表上创建的非汇集索引,每一个非汇集索引行里都包含一个物理指针映射回堆表里的行记录。这个物理指针是[文件号:页号:槽号](file:page:solt)的结构,所以在读取页的时候,能够找到堆表里的对应行,再经过行偏移表里槽号里的偏移量,就能够在页里读取到对应的行记录。若是咱们要修改页中间的记录,咱们并不必定须要重组整个页,咱们只要修改偏移表里偏移量便可。
在页头咱们看到当前页面还有7644 bytes能够用,咱们一块儿来验证下。
(8 * 1024) - 96 - (217 * 2)-(7 * 2)-(2 * 2)=7644 bytes
8 * 1024 = 页的总大小,8K
96 = 页头大小 96 bytes
217 * 2 = 每条记录的总长 * 记录数
7 * 2 = 每条记录的系统行开销 * 记录数
2 * 2 = 行偏移表里每槽占用字节数 * 记录数
如今咱们已经知道了页的结构,咱们一块儿来小结下。
页是 8KB 的大小,即 8192 bytes,固定 96 bytes的大小给页头使用,接下来是具体的数据以槽的方式存储。数据记录的最大长度是 8060 bytes(包括 7 bytes的系统行开销),所以一条记录中你拥有的最大字节数是 8053 bytes。下列的表建立语句会失败。
1 CREATE TABLE Maxsize( 2 id CHAR(8000) NOT NULL, 3 id1 CHAR(54) NOT NULL 4 )
剩下的 36 bytes (8192-96-8060)保留给槽数组(Slot array)或者任何转发行返回指针(forwarding row back pointer)(每条10 bytes)。这就意味一个页不必定就能保存18(36/2)条记录。槽数组(Slot array)根据你的记录数从下往上增加。若是记录长度小,页里就能够存储更多的记录,偏移表也会自下而上占用更多的空间。