数据页面和数据行

数据库中的空间分为逻辑8KB页面。 这些页面从零开始连续编号,可以通过指定文件ID和页码来引用它们。 页面编号始终是连续的,这样当SQL Server增长数据库文件时,新页面将从文件中的最高页码加1开始编号。 同样,当SQL Server收缩文件时,它会从文件中删除编号最大的页面。
SQL SERVER中的数据存储
一般来说,SQL Server存储和处理数据库中的数据有三种不同的方式或技术。 使用经典的基于行的存储,数据存储在数据行中,这些数据行将来自所有列的数据组合在一起。

SQL Server 2012引入了列存储索引和基于列的存储。 该技术以每列而不是每行存储数据。 我们将在本书的第七部分中介绍基于列的存储。

最后,SQL Server 2014中引入了一组内存技术,并在SQL Server 2016中得到了进一步改进。尽管它们将数据保留在磁盘上以实现冗余,但它们的存储格式与基于行和列的存储格式非常不同 存储。 我们将在本书的第八部分讨论内存中的技术。

本书的这一部分主要关注基于行的存储和经典的B-Tree索引和堆。
在这里插入图片描述

一个96字节的页眉包含有关页面的各种信息,例如页面所属的对象,页面上可用的行数和可用空间量,如果页面是,则指向上一页和下一页的链接在索引页链中,依此类推。

页眉之后是存储实际数据的区域。其次是自由空间。最后,有一个插槽数组,它是一个双字节条目块,指示相应数据行在页面上开始的偏移量。

slot数组指定页面上数据行的逻辑顺序。如果页面上的数据需要按索引键的顺序排序,则SQL Server不会对页面上的数据行进行物理排序,而是根据索引排序顺序填充插槽数组。插槽0(图1-6中最右侧)存储页面上具有最低键值的数据行的偏移量;插槽1,第二低键值;等等。我们将在下一章更深入地讨论索引。

SQL Server提供了一组丰富的系统数据类型,可以在逻辑上分为两个不同的组:固定长度和可变长度。固定长度数据类型(如int,datetime,char等)始终使用相同数量的存储空间,无论其值如何,即使它为NULL也是如此。例如,int列始终使用4个字节,nchar(10)列始终使用20个字节来存储信息。

相比之下,可变长度数据类型(如varchar,varbinary和其他一些数据类型)使用存储数据所需的存储空间,以及两个额外字节。例如,nvarchar(4000)列只使用12个字节来存储五个字符的字符串,在大多数情况下,两个字节用于存储NULL值。我们将讨论可变长度列不使用存储空间的情况对于本章后面的NULL值。

让我们看一下数据行的结构,如图1-7所示。 让我们看一下数据行的结构,如图1-7所示。
在这里插入图片描述

                                      图1-7 数据行结构

该行的前两个字节(称为状态位A和状态位B)是包含有关该行的信息的位图,例如行类型,如果该行已被逻辑删除(幻影),并且该行具有NULL值,可变长度列和版本控制标记。

行中接下来的两个字节用于存储数据的固定长度部分的长度。它们后面跟着固定长度的数据。

在固定长度数据部分之后,存在空位图,其包括两个不同的数据元素。第一个双字节元素是行中的列数。第二个是空位图数组。该数组对表的每一列使用一位,无论它是否可为空。

即使表没有可空列,也会在堆表或聚簇索引叶行中的数据行中始终存在空位图。但是,当索引中没有可为空的列时,空位图不存在于非叶索引行中,也不存在于非聚簇索引的叶级行中。

在空位图之后,存在该行的可变长度数据部分。它从行中的两个字节数量的可变长度列开始,后跟列偏移数组。 SQL Server为行中的每个可变长度列存储两个字节的偏移值,即使该值为NULL也是如此。接下来是数据的实际可变长度部分。最后,在行的末尾有一个可选的14字节版本控制标记。此标记用于需要行版本控制的操作,例如联机索引重建,乐观隔离级别,触发器和其他一些操作。

注意我们将在第6章讨论索引维护,第9章讨论触发器,第21章讨论乐观隔离级别。

让我们创建一个表,用一些数据填充它,然后查看实际的行数据。 代码如清单1-4所示。 复制功能重复作为第一个参数提供的字符十次。

清单1-4 数据行格式:表创建

create table dbo.DataRows
(
ID int not null,
Col1 varchar(255) null,
Col2 varchar(255) null,
Col3 varchar(255) null
);
insert into dbo.DataRows(ID, Col1, Col3) values (1,replicate('a',10),replicate('c',10));
insert into dbo.DataRows(ID, Col2) values (2,replicate('b',10));
dbcc ind
(
'SQLServerInternals' /*Database Name*/
,'dbo.DataRows' /*Table Name*/
,-1 /*Display information for all pages of all indexes*/
);

未记录但着名的DBCC IND命令返回有关表页分配的信息。 您可以在图1-8中看到此命令的输出。
在这里插入图片描述

                                    图1-8  DBCC IND输出

有两个属于该表的页面。 第一个,PageType = 10,是一种特殊类型的页面,称为IAM分配映射。 此页面跟踪属于特定对象的页面。 但是,现在不要关注它,因为我们将在本章后面介绍分配映射页面。

注意SQL Server 2012引入了另一个未记录的数据管理功能(DMF),即

ys.dm_db_database_page_allocations,可用作DBCC IND命令的替代。 与DBCC IND相比,此DMF的输出提供了更多信息,并且可以与其他系统DMV和/或目录视图结合使用。

PageType = 1的页面是包含数据行的实际数据页面。 PageFID和PagePID列显示页面的实际文件和页码。 您可以使用另一个未记录的命令DBCC PAGE来检查其内容,如清单1-5所示。

清单1-5。 数据行格式:DBCC PAGE调用
— 将DBCC PAGE输出重定向到控制台

dbcc traceon(3604);
dbcc page
(
'SqlServerInternals' /*Database Name*/
,1 /*File ID*/
,214643 /*Page ID*/
,3 /*Output mode: 3 - display page header and row details */
);

清单1-6。 第一行的DBCC PAGE输出

Slot 0 Offset 0x60 Length 39
Record Type = PRIMARY_RECORD  Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 39
Memory Dump @0x000000000EABA060
0000000000000000: 30000800 01000000 04000403 001d001d 00270061 0........'.a
0000000000000014: 61616161 61616161 61636363 63636363 636363 aaaaaaaaacccccccccc
Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4
ID = 1
Slot 0 Column 2 Offset 0x13 Length 10 Length (physical) 10
Col1 = aaaaaaaaaa
Slot 0 Column 3 Offset 0x0 Length 0 Length (physical) 0
Col2 = [NULL]
Slot 0 Column 4 Offset 0x1d Length 10 Length (physical) 10
Col3 = cccccccccc

让我们更详细地看一下数据行,如图1-9所示。

在这里插入图片描述

                                 图1-10  第二个数据行数据

清单1-7 第二行的DBCC PAGE输出

Slot 1 Offset 0x87 Length 27
Record Type = PRIMARY_RECORD   Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 27
Memory Dump @0x000000000EABA087
0000000000000000: 30000800 02000000 04000a02 0011001b 00626262 0................bbb
0000000000000014: 62626262 626262                                    bbbbbbb
Slot 1 Column 1 Offset 0x4 Length 4 Length (physical) 4
ID = 2
Slot 1 Column 2 Offset 0x0 Length 0 Length (physical) 0
Col1 = [NULL]
Slot 1 Column 3 Offset 0x11 Length 10 Length (physical) 10
Col2 = bbbbbbbbbb
Slot 1 Column 4 Offset 0x0 Length 0 Length (physical) 0
Col3 = [NULL]

第二行中的NULL位图表示二进制值00001010,表示Col1和Col3为NULL。 尽管该表具有三个可变长度列,但行中可变长度列的数量表示偏移数组中只有两个列/槽。 SQL Server不维护有关行中的尾随NULL可变长度列的信息。

提示您可以通过以一种方式创建表来减小数据行的大小,在这种方式中,通常存储空值的可变长度列被定义为CREATE TABLE语句中的最后一列。 这是CREATE TABLE语句中列的顺序很重要的唯一情况。

固定长度数据和内部属性必须适合单个数据页面上可用的8,060字节。 如果不是这种情况,SQL Server不允许您创建表。 例如,清单1-8中的代码产生错误。

清单1-8。 创建一个数据行大小超过8,060字节的表

create table dbo.BadTable
(
Col1 char(4000),
Col2 char(4060)
)
Msg 1701, Level 16, State 1, Line 1
Creating or altering table 'BadTable' failed because the minimum row size would be 8,067,
including 7 bytes of internal overhead. This exceeds the maximum allowable table row size of
8,060 bytes.

大对象存储

即使行的固定长度数据和内部属性必须适合单个页面,SQL Server也可以将可变长度数据存储在不同的数据页上。 根据数据类型和长度,有两种不同的方式来存储数据。
行溢出存储
SQL Server在称为行溢出页面的特殊页面上存储不超过8,000字节的可变长度列数据。 让我们创建一个表,并使用清单1-9中所示的数据填充它。

清单1-9。 行溢出数据:创建表

create table dbo.RowOverflow
(
ID int not null,
Col1 varchar(8000) null,
Col2 varchar(8000) null
);
insert into dbo.RowOverflow(ID, Col1, Col2) values
(1,replicate('a',8000),replicate('b',8000));

如您所见,即使数据行大小超过8,060字节,SQL Server也会创建表并插入数据行而不会出现任何错误。 让我们看看使用DBCC IND命令的表页分配。 结果如图1-11所示
在这里插入图片描述

                          图1-11  行溢出数据:DBCC IND结果。

现在您可以看到两组不同的IAM和数据页面。 PageType = 3的数据页表示存储行溢出数据的数据页。

让我们看一下数据页面214647,它是存储主行数据的行内数据页面。 页面的DBCC PAGE命令的部分输出(1:214647)如清单1-10所示。

清单1-10。 行溢出数据:IN_ROW数据的DBCC PAGE结果

Slot 0 Offset 0x60 Length 8041
Record Type = PRIMARY_RECORD   Record Attributes = NULL_BITMAP VARIABLE_COLUMNS
Record Size = 8041
Memory Dump @0x000000000FB7A060
0000000000000000: 30000800 01000000 03000002 00511f69 9f616161 0..........  Q.iŸaaa
0000000000000014: 61616161 61616161 61616161 61616161 61616161 aaaaaaaaaaaaaaaaaaaa
<Skipped>
0000000000001F40: 61616161 61616161 61616161 61616161 61020000 aaaaaaaaaaaaaaaaa…
0000000000001F54: 00010000 00290000 00401f00 00754603 00010000 .....)…@…uF.........
0000000000001F68: 00

如您所见,SQL Server将Col1数据存储在行中。 但是,Col2数据已被24字节值替换。 前16个字节用于存储行外存储元数据,例如类型,数据长度和一些其他属性。 最后一个8字节的块是指向行溢出页面上行的实际指针,该行由文件,页面和插槽号组成。 图1-12详细说明了这一点。 请记住,所有信息都以字节交换顺序存储。
在这里插入图片描述

      图1-12  行溢出数据:行溢出页面指针结构

如您所见,插槽号为0,文件号为1,页码为十六进制值0x00034675,为十进制214645。 页码与DBCC IND结果匹配,如图1-10所示。

页面的DBCC PAGE命令的部分输出(1:214645)如清单1-11所示。

清单1-11。 行溢出数据:行溢出数据的DBCC PAGE结果

Blob row at: Page (1:214645) Slot 0 Length: 8014 Type: 3 (DATA)
Blob Id:2686976
0000000008E0A06E: 62626262 62626262 62626262 62626262 bbbbbbbbbbbbbbbb
0000000008E0A07E: 62626262 62626262 62626262 62626262 bbbbbbbbbbbbbbbb

如您所见,Col2数据存储在页面的第一个插槽中。

LOB存储

对于text,ntext或image列,SQL Server默认将数据存储在行外。 它使用另一种页面称为LOB数据页面。

注意您可以使用“text in row”表选项将此行为控制到一定程度。 例如,execsp_table_option dbo.MyTable,‘text in row’,200强制SQL Server在行内存储小于或等于200字节的LOB数据。 大于200字节的LOB数据将存储在LOB页面中。

逻辑LOB数据结构如图1-13所示。
在这里插入图片描述

                                  图1-13  LOB数据:逻辑结构

与行溢出数据一样,有一个指向另一条信息的指针,称为LOB根结构,它包含一组指向其他数据页和行的指针。 当LOB数据小于32 KB并且可以容纳五个数据页时,LOB根结构包含指向LOB数据的实际块的指针。否则,LOB树开始包含额外的中间级指针,类似于索引B-Tree,我们将在下一章中讨论。

让我们创建表并插入一行数据,如清单1-12所示。 我们需要将replicate函数的第一个参数转换为varchar(max)。 否则,复制函数的结果将限制为8,000个字节。

清单1-12 LOB数据:表创建

create table dbo.TextData
(
ID int not null,
Col1 text null
);
insert into dbo.TextData(ID, Col1) values (1, replicate(convert(varchar(max),'a'),16000));

该表的页面分配如图1-14所示。
在这里插入图片描述

                                   图1-14  LOB数据:DBCC IND结果

如您所见,该表有一个用于行内数据的数据页和三个用于LOB数据的数据页。 我不打算检查行内分配的数据行结构; 它类似于行溢出分配。 但是,通过LOB分配,表在指针中存储较少的元数据信息,并使用16个字节而不是行溢出指针所需的24个字节。

存储LOB根结构的页面的DBCC PAGE命令的结果如清单1-13所示。

清单1-13。 LOB数据:具有LOB根结构的LOB页面的DBCC PAGE结果

Blob row at: Page (1:3046835) Slot 0 Length: 84 Type: 5 (LARGE_ROOT_YUKON)
Blob Id: 131661824 Level: 0 MaxLinks: 5 CurLinks: 2
Child 0 at Page (1:3046834) Slot 0 Size: 8040 Offset: 8040
Child 1 at Page (1:3046832) Slot 0 Size: 7960 Offset: 16000

如您所见,有两个指向带有LOB数据块的其他页面的指针,它们类似于清单1-11中所示的blob数据。

SQL Server存储(MAX)列中数据的格式(如varchar(max),nvarchar(max)和varbinary(max))取决于实际数据大小。 SQL Server尽可能将其存储在行中。当行内分配不可能,并且数据大小小于或等于8,000字节时,它被存储为行溢出数据。超过8,000字节的数据存储为LOB数据。

不推荐使用重要的text,n text和image数据类型,它们将在SQL Server的未来版本中删除。请改用varchar(max),nvarchar(max)和varbinary(max)列。

还值得一提的是,SQL Server始终使用行内分配存储适合单个页面的行。当页面没有足够的可用空间来容纳一行时,SQL Server会分配一个新页面并将行放在那里而不是将其放在半满页面上并将一些数据移动到行溢出页面。

-------------------译至《Pro SQL Server Internals, 2nd edition》。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值