页和区体系结构指南
适用范围:SQL Server Azure SQL 数据库 Azure SQL 托管实例 Azure Synapse Analytics Analytics Platform System (PDW)
“页”是 SQL Server 中的基本数据存储单位。 “盘区”是由八个物理上连续的页构成的集合。 区有助于有效管理页。 本指南介绍用于管理所有版本的 SQL Server 中的页和区的数据结构。 要设计和开发高效执行的数据库,了解页和区的体系结构是很重要的。
页和区
SQL Server 中数据存储的基本单位是页。 为数据库中的数据文件(.mdf 或 .ndf)分配的磁盘空间可以从逻辑上划分成页(从 0 到 n 连续编号)。 磁盘 I/O 操作在页级执行。 也就是说,SQL Server 读取或写入所有数据页。
区是八个物理上连续的页的集合,用来有效地管理页。 所有页面都组织为盘区。
页
在常规书籍中,所有内容都是写在页上的。 与书籍类似,SQL Server 将在页上写入所有数据行,所有数据页的大小均为 8 KB。 在书籍中,大多数页都包含数据(书籍的主要内容),某些页包含有关内容的元数据(例如目录和索引)。 SQL Server 也是如此:大多数页包含由用户存储的实际数据行;这些称为“数据页”和“文本/图像页”(在特殊情况下)。 “索引页”包含有关数据所在位置的索引引用。 最后还有一些“系统页”,用于存储有关数据组织的各种元数据。
每页的开头是 96 字节的标头,用于存储有关页的系统信息。 此信息包括页码、页类型、页的可用空间以及拥有该页的对象的分配单元 ID。
下表说明了 SQL Server 数据库的数据文件中所使用的页类型。
页面类型 | 目录 |
---|---|
数据 | 当行中的文本设置为 ON 时包含所有数据的数据行,text、ntext、image、nvarchar(max)、varchar(max)、varbinary(max) 和 xml 数据除外。 |
Index | 索引条目。 |
文本/图像 | 大型对象数据类型:text、ntext、image、nvarchar(max)、varchar(max)、varbinary(max) 和 xml 数据。 数据行超过 8 KB 时的可变长度列:varchar、nvarchar、varbinary 和 sql_variant。 |
全局分配映射表 (GAM) 共享全局分配映射表 (SGAM) |
有关区是否分配的信息。 |
页可用空间 (PFS) | 有关页分配和页的可用空间的信息。 |
索引分配映射 (IAM) | 有关每个分配单元中表或索引所使用的区的信息。 |
大容量更改映射表 (BCM) | 有关每个分配单元中自最后一条 BACKUP LOG 语句之后的大容量操作所修改的区的信息。 |
差异更改映射表 (DCM) | 有关每个分配单元中自最后一条 BACKUP DATABASE 语句之后更改的区的信息。 |
注意
日志文件不包含页, 而是包含一系列没有固定大小的日志记录。
数据行在页中从标头开始按顺序存储。 页的末尾是行偏移表,对于页中的每一行,每个行偏移表都包含一个条目。 每个行偏移量条目记录该行第一个字节与页首的距离。 因此,行偏移量有助于 SQL Server 快速在页面上定位行。 行偏移表中的条目的顺序与页中行的顺序相反。
大型行支持
行不能跨页,但是行的部分内容可以移出行所在的页,因此行实际可能非常大。 页中单个行中的最大数据量和开销是 8,060 字节。 但是,这不包括用文本/图像页类型存储的数据。
对于包含 varchar、nvarchar、varbinary 或 sql_variant 列的表,可以放宽此限制。 当表中所有固定列和可变列的总行大小超过 8,060 字节这一限制时,SQL Server 将从最大宽度的列开始,动态将一个或多个可变长度列移动到 ROW_OVERFLOW_DATA 分配单元中的页。
每当插入或更新操作将行的总大小增大到超过限制的 8,060 字节时,将会执行此操作。 将列移动到 ROW_OVERFLOW_DATA 分配单元中的页后,将在 IN_ROW_DATA 分配单元中的原始页上维护 24 字节的指针。 如果后续操作减小了行的大小,SQL Server 会动态将列移回到原始数据页。
行溢出注意事项
如果可变长度数据类型字段的合计大小超过 8060 字节这一限制,则行不能驻留在多个页上并且可能溢出。 例如,可以创建具有如下两列的表:一个 varchar(7000),另一个 varchar (2000)。 每列单独都不超过 8060 字节,但如果填满每列的整个宽度,则这两列合计将超过该限制。 SQL Server 可以将 varchar(7000) 可变长度列动态移动到 ROW_OVERFLOW_DATA 分配单元中的页。 合并每行超过 8,060 字节的 varchar、nvarchar、varbinary、sql_variant 或 CLR 用户定义类型列时,请注意下列事项:
如果更新操作使记录变长,大型记录将被动态移动到另一页。 如果更新操作使记录变短,记录可能会移回 IN_ROW_DATA 分配单元中的原始页。
执行查询和其他选择操作(例如,对包含行溢出数据的大型记录进行排序或合并)将延长处理时间,因为这些记录将同步处理,而不是异步处理。
因此,如果要设计的表中包含多个 varchar、nvarchar、varbinary、sql_variant 或 CLR 用户定义类型列,请考虑可能溢出的行所占百分比,以及可能查询这些溢出数据的频率。 如果可能需要经常查询行溢出数据中的许多行,请考虑对表格进行规范化处理,以使某些列移动到另一个表中。 然后可以在异步 JOIN 操作中执行查询。
对于 varchar、nvarchar、varbinary、sql_variant 或 CLR 用户定义类型列,单个列的长度仍然必须控制在 8,000 字节的限制之内。 只有它们的合并长度可以超过表的 8,060 字节的行限制。
其他数据类型列的和(包括 char 和 nchar 数据)必须控制在 8,060 字节的行限制之内。 大型对象数据也不受 8,060 字节行限制的制约。
聚集索引的索引键不能包含在 ROW_OVERFLOW_DATA 分配单元中具有现有数据的 varchar 列。 如果对 varchar 列创建了聚集索引,并且 IN_ROW_DATA 分配单元中存在现有数据,则对该列执行的将数据推送到行外的后续插入或更新操作将会失败。 有关分配单元的详细信息,请参阅 SQL Server 以及 Azure SQL 索引体系结构和设计指南。
可以包括包含行溢出数据的列,作为非聚集索引的键列或非键列。
对于使用稀疏列的表,记录大小限制为 8,018 字节。 转换后的数据加上现有记录数据超过 8,018 字节时,会返回 MSSQLSERVER ERROR 576。 在稀疏和非稀疏类型之间转换列时,数据库引擎会保存当前记录数据的副本。 这样,记录所需的存储会临时加倍。
若要获得有关可能包含行溢出数据的表或索引的信息,请使用 sys.dm_db_index_physical_stats 动态管理函数。
Extents
区是管理空间的基本单位。 一个区是八个物理上连续的页(即 64 KB)。 因此 SQL Server 数据库中每兆字节有 16 个盘区。
SQL Server 有两种类型的区:
- 统一盘区,由单个对象所有。盘区中的所有八页只能由所属对象使用。
- 混合盘区,最多可由八个对象共享。 区中八页的每页可由不同的对象所有。
在 SQL Server 2014 (12.x) 以前(包括该版本),数据库引擎不会将整个盘区分配给具有少量数据的表。 新表或索引通常从混合区分配页。 当表或索引增长到 8 页时,将变成使用统一区进行后续分配。 如果对现有表创建索引,并且该表包含的行足以在索引中生成 8 页,则对该索引的所有分配都使用统一区进行。
从 SQL Server 2016 (13.x) 开始,用户数据库和 tempdb
中大多数分配的默认值都是使用统一盘区,但属于 IAM 链前八页的分配除外。 master
、msdb
和 model
数据库的分配仍遵循以前的行为。
注意
在 SQL Server 2014 (12.x) 以前(包括该版本的 SQL Server),可以使用跟踪标志 (TF) 1118 将默认分配更改为始终使用统一盘区。 有关此跟踪标志的详细信息,请参阅 DBCC TRACEON - 跟踪标志。
从 SQL Server 2016 (13.x) 开始,tempdb
和所有用户数据库都将自动启用 TF 1118 提供的功能。 对于用户数据库,此行为受 ALTER DATABASE
的 SET MIXED_PAGE_ALLOCATION
选项控制,同时默认值设置为 OFF,且 TF 1118 无效。 有关详细信息,请参阅 ALTER DATABASE SET 选项。
从 SQL Server 2012 (11.x) 开始,sys.dm_db_database_page_allocations
系统函数可以报告数据库、表、索引和分区的页分配信息。
重要
未记录 sys.dm_db_database_page_allocations
系统函数,并且可能会发生更改。 不保证兼容性。
从 SQL Server 2019 (15.x) 开始,sys.dm_db_page_info 系统函数可用,并将返回有关数据库中页的信息。 该函数将返回包含页中标头信息的一行,包括 object_id
、index_id
和 partition_id
。 在大多数情况下,此函数取代了使用 DBCC PAGE
的需要。
管理盘区分配和可用空间
管理区分配情况并跟踪可用空间的 SQL Server 数据结构有一个相对简单的结构。 该功能有以下优点:
可用空间信息被紧密压缩,因此包含此信息的页相对较少。
这将会减少检索分配信息时所需的磁盘读取量,从而提高速度。 同时还可增加分配页保留在内存中的机会并且不需要更多的读操作。
大多数分配信息不是链在一起的。 这就简化了对分配信息的维护。
可以快速执行每个页的分配或释放。 这将减少需要分配页或释放页的并发任务之间的争用。
管理盘区分配
SQL Server 使用两种类型的分配映射表来记录区的分配:
全局分配映射表 (GAM)
GAM 页记录已分配的区。 每个 GAM 包含 64,000 个区,相当于近 4 GB 的数据。 GAM 用 1 个位来表示所涵盖区间内每个盘区的状态。 如果位为
1
,则该盘区可用;如果位为0
,则该盘区已分配。共享全局分配映射表 (SGAM)
SGAM 页记录当前用作混合区且至少有一个未使用的页的区。 每个 SGAM 包含 64,000 个区,相当于近 4 GB 的数据。 SGAM 用 1 个位来表示所涵盖区间内的每个盘区的状态。 如果位为
1
,则该盘区正用作混合盘区并且有可用页。 如果位为0
,则该盘区未用作混合盘区,或者虽然用作混合盘区但其所有页均在使用中。
根据区当前的使用情况,GAM 和 SGAM 中每个区具有以下位模式。
区的当前使用情况 | GAM 位设置 | SGAM 位设置 |
---|---|---|
可用,未使用 | 1 | 0 |
统一区或已满的混合区 | 0 | 0 |
具有可用页的混合区 | 0 | 1 |
这将简化区管理算法。
- 为了分配统一盘区,数据库引擎将在 GAM 中搜索为
1
的位,并将其设置为0
。 - 为了查找具有可用页的混合盘区,数据库引擎将在 SGAM 中搜索为
1
的位。 - 为了分配混合盘区,数据库引擎将在 GAM 中搜索为
1
的位,并将其设置为0
;然后也将 SGAM 中相应的位设置为1
。 - 为了解除分配某个盘区,数据库引擎将确保 GAM 位设置为
1
,而 SGAM 位设置为0
。
数据库引擎在内部使用的算法比本文中介绍的更复杂,因为数据库引擎会在数据库中平均分布数据。 但是,由于无需管理区分配信息链,因此即使是实际算法也会被简化。
跟踪可用空间
“页可用空间 (PFS)”页记录每页的分配状态,是否已分配单个页以及每页的可用空间量。 PFS 每页用 1 个字节来记录该页是否已分配。如果已分配,则记录该页是为空、已满 1% 到 50%、已满 51% 到 80%、已满 81% 到 95% 还是已满 96% 到 100%。
将区分配给对象后,数据库引擎将使用 PFS 页来记录区中的哪些页已分配或哪些页可用。 当数据库引擎必须分配新页时,将使用此信息。 仅堆和文本/图像页会记录有关页中可用空间量的信息。 当数据库引擎必须找到一个具有可用空间的页来保存新插入的行时,将使用此信息。 索引不要求跟踪页的可用空间,因为插入新行的点是由索引键值设置的。
它将在数据文件中按各自的区域间隔添加一个新的 PFS、GAM 或 SGAM 页面。 因此,将出现一个新的 PFS 页面,在第一个 PFS 页面之后为 8,088 页,在间隔 8,088 页后为另一个 PFS 页面。 举例说明,第 1 页为 PFS 页面,第 8088 页为 PFS 页面,第 16176 页为 PFS 页面,以此类推。
也将出现一个新的 GAM 页面,在第一个 GAM 页面之后为 64,000 个盘区,在间隔 64,000 个盘区后为另一个 GAM 页面,以此类推。 同样,将出现一个新的 SGAM 页面,在第一个 SGAM 页面之后为 64,000 个盘区,在间隔 64,000 个盘区后为另一个 SGAM 页面。
下图显示了数据库引擎用来分配和管理区的页顺序。
管理对象使用的空间
“索引分配映射 (IAM)”页将映射分配单元使用的数据库文件中 4-GB 部分中的盘区。 分配单元有下列三种类型:
IN_ROW_DATA
用于存储堆分区或索引分区。
LOB_DATA
用于存储大型对象 (LOB) 数据类型,如 xml、varbinary(max) 和 varchar(max)。
ROW_OVERFLOW_DATA
用于存储超过 8,060 字节行大小限制的 varchar、nvarchar、varbinary 或 sql_variant 列中存储的可变长度数据。
堆或索引的每个分区至少包含一个 IN_ROW_DATA 分配单元。 根据堆或索引的架构,可能还包含一个 LOB_DATA 或 ROW_OVERFLOW_DATA 分配单元。
一个 IAM 页在文件中的范围为 4 GB,与 GAM 或 SGAM 页的范围相同。 如果分配单元包含来自多个文件的区,或者超过一个文件的 4 GB 范围,那么一个 IAM 链中将链接多个 IAM 页。 因此,每个分配单元在有区的每个文件中至少有一个 IAM 页。 如果分配给分配单元的文件中的盘区的范围超过了一个 IAM 页能够记录的范围,一个文件中也可能会有多个 IAM 页。
IAM 页根据需要分配给每个分配单元,在文件中的位置也是随机的。 sys.system_internals_allocation_units
系统视图指向分配单元的第一个 IAM 页。 该分配单元的所有 IAM 页都链接到一个 IAM 链中。
重要
sys.system_internals_allocation_units
系统视图仅供内部使用,随时可能更改。 不保证兼容性。 此视图在 Azure SQL 数据库中不可用。
IAM 页有一个标头,指明 IAM 页所映射的盘区范围的起始盘区。 IAM 页中还有一个大位图,其中每个位代表一个区。 位图中的第一个位代表范围内的第一个区,第二个位代表第二个区,依此类推。 如果某个位是 0
,则其所代表的盘区将不会分配给拥有该 IAM 页的分配单元。 如果该位是 1
,则其所代表的盘区将分配给拥有该 IAM 页的分配单元。
如果数据库引擎必须在当前页中插入新行,而当前页中没有可用空间,则将使用 IAM 和 PFS 页查找要将该行分配到的页,或者(对于堆或文本/图像页)查找具有足够空间容纳该行的页。 数据库引擎使用 IAM 页查找分配给分配单元的区。 对于每个盘区,数据库引擎将搜索 PFS 页,以查看是否有可用的页。 每个 IAM 和 PFS 页覆盖许多数据页,因此一个数据库内只有很少的 IAM 和 PFS 页。 这意味着 IAM 和 PFS 页通常位于内存中的 SQL Server 缓冲池中,以便可以快速搜索它们。 对于索引,新行的插入点由索引键设置,但是当需要新页面时,将发生先前描述的过程。
仅当数据库引擎不能在现有盘区中快速找到拥有足够空间的页来容纳要插入的行时,才将新盘区分配给分配单元。
比例填充分配
数据库引擎使用“比例分配算法”从文件组的可用盘区中分配盘区。 如果同一文件组内有两个文件,其中一个文件的可用空间是另一个文件的两倍,那么每从后一个文件分配一页,就将从前一个文件分配两页。 这意味着文件组内的每个文件应该有近似的空间使用百分比。
跟踪已修改的盘区
SQL Server 使用两个内部数据结构来跟踪被大容量复制操作修改的盘区,以及自上次完整备份后修改的盘区。 这些数据结构极大地加快了差异备份的速度。 当数据库使用大容量日志恢复模式时,这些数据结构也可以加快将大容量复制操作记录至日志的速度。 与 GAM 和 SGAM 页类似,这些结构也是位图,其中的每一位代表一个单独的盘区。
差异更改映射表 (DCM)
这样便可跟踪自上次执行
BACKUP DATABASE
语句后更改过的盘区。 如果盘区的位是1
,则说明自上次执行BACKUP DATABASE
语句后盘区已被修改。 如果位是0
,则说明盘区没有被修改。差异备份只读取 DCM 页便可以确定已修改的区。 这样大大减少了差异备份必须扫描的页数。 运行差异备份所需的时间与自上次执行
BACKUP DATABASE
语句之后修改的盘区数成正比,而不是与整个数据库的大小成正比。大容量更改映射表 (BCM)
跟踪自上次执行
BACKUP LOG
语句后,被大容量日志记录操作修改的盘区。 如果某个盘区的位是1
,则说明自上次执行BACKUP LOG
语句后,该盘区已经被大容量日志记录操作修改。 如果该位是0
,则说明该盘区未被大容量日志记录操作修改。尽管所有数据库中都显示 BCM 页,但只有在数据库使用大容量日志记录恢复模式时,才会与 BCM 页有关。 在此恢复模式中,当执行
BACKUP LOG
时,备份进程将扫描 BCM 查找已经修改的盘区。 然后,将那些区包括在日志备份中。 如果从数据库备份和一系列事务日志备份还原数据库,这将恢复大容量日志记录操作。 在使用简单恢复模式的数据库中,BCM 页是不相关的,因为大容量日志记录操作不会记入日志。 在使用完整恢复模式的数据库中,BCM 页同样不相关,因为该恢复模式会将大容量日志记录操作视为有完整日志记录的操作。
DCM 页和 BCM 页的间隔与 GAM 和 SGAM 页的间隔相同,都是 64,000 个区。 在物理文件中,DCM 和 BCM 页位于 GAM 和 SGAM 页之后,如下所示: