SQL Server内部如何管理对象的数据Page?
一个表或Index使用的数据页空间是由IAM Page Chain来管理的。SQL Server 使用一个IAM(Index Allocation Map)Page来管理数据库文件中最多4GB的空间,一个IAM Page映射文件中4GB大小中的区(Extent),区由8个物理地址连续的Page构成,是由分配单元(Allocation Unit)负责分配的,分配单元有三种类型:
- IN_ROW_DATA:保存堆(heap)或索引(index)的一个分区(partition)
- LOB_DATA:保存LOB(Large Object)数据类型,LOB数据类型是:xml,、varbinary(max)、varchar(max)和nvarchar(max)
- ROW_OVERFLOW_DATA:保存变长数据(varchar, nvarchar, varbinary, 或 sql_variant )类型,且超过8060Bytes的列。
每一个heap或index都包含至少一个IN_ROW_DATA 分配单元,可能包含 LOB_DATA 或 ROW_OVERFLOW_DATA 分配单元。
一,对大数据行的支持
在SQL Server中,一个数据行不能跨越Page,但是,可以把行的某些部分移除该行所在的页面。一个Page上的单行可以包含的最大数据量和管理开销(Overhead)之和是8060字节。对于包含变长类型(varchar,nvarchar,varbinary或sql_variant)列的表,当一行中的所有固定大小列和变长列的总大小超过8,060字节的限制时,SQL Server将从宽度最大的列开始,把一个或多个可变长度列移动到ROW_OVERFLOW_DATA分配单元中的页面,这样一行数据实际上存储在两个或多个Page上。
只要插入或更新操作把行的总大小增加到超过8,060字节的限制,SQL Server就自动执行此操作,把最大的变长列移动到ROW_OVERFLOW_DATA分配单元中的页面中,只把存在于IN_ROW_DATA分配单元中原始页面上的24字节的指针保留下来,该指针指向ROW_OVERFLOW_DATA 页面。如果后续的更新操作导致该行减少到小于8,060字节,那么SQL Server自动把列移回到原始数据页中。
二,数据表使用的Page
一个表或Index使用的空间是由数据Page和相应的管理Page构成的,其中数据页是由IAM Page Chain来管理的,而IAM Pages也属于对象占用的硬盘空间,是为了管理数据页而必须付出的代价。一个IAM Page映射文件中的4 GB范围,并且与GAM或SGAM页的覆盖范围相同。 如果分配单元(allocation unit)包含来自多个文件或一个文件的大于4GB范围的区(Extent),那么分配单元存在多个IAMP Page,且这些IAM页面连接成一个IAM 链。IAM Page链是双向链,每一个Page中都存在向前和向后的指针,通过这两个指针找到前一个页面和后一个页面。
对于每一个数据库文件,如果分配单元在该文件中存在区(Extent),那么该分配单元在该文件上至少存在一个IAM页。也就是说,每个分配单元在其具有Extent的每个文件中至少拥有一个IAM页。 如果文件上分配给分配单元的扩展区范围超出了单个IAM页可以记录的范围(4GB),则在该文件上也可能有多个IAM页。
IAM Page根据每个分配单元的需要进行分配,IAM Page在文件中的位置是随机的,SQL Server提供一个非正式的内部使用的视图 sys.system_internals_allocation_units,该视图中的first_page字段指向分配单元的第一个IAM页面,可以认为是IAM 链(Chain)的根(Root)页面。由于分配单元的所有IAM页面都链接在一起,通过根页面里的指针,顺着双向链,可以遍历分配单元的所有IAM Page,进而可以查找到表或索引的所有数据页。
对于视图返回的字段 container_id,用于表示跟分配单元相关链的存储容器,表示的是hobt_id 或 partition_id,由type值来确定,匹配的规则如下:
- If type = 1 or 3, container_id = sys.partitions.hobt_id.
- If type is 2, then container_id = sys.partitions.partition_id.
- 0 = Allocation unit marked for deferred drop
SQL Server 还提供了一个正式视图 sys.allocation_units,用于查看分配单元的信息,该视图和sys.partitions关联起来,可以用于查看跟该分配单元相关的分区数据:
select * from sys.allocation_units u cross join sys.partitions p where u.type in (1,3) and u.container_id = p.hobt_id or u.type=2 and u.container_id= p.partition_id
三,空间的延迟删除
当您删除或重建大型索引,或者删除或truncate大型表时,数据库引擎会推迟实际的page回收,直到事务提交为止。 延迟删除操作不会立即释放被分配的空间,因此,在drop或truncate大对象之后,sys.allocation_units 实时返回的值可能无法正确反映实际的可用磁盘空间。这也是 container_id =0 表示分配单元被标记为延迟删除的原因。
参考文档: