欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

SQL Server Page结构深入分析

程序员文章站 2022-04-09 21:35:02
sql server存储数据的基本单元是page,每一个page的大小是8kb,数据文件是由page构成的。在同一个数据库上,每一个page都有一个唯一的资源标识,标识符由...

sql server存储数据的基本单元是page,每一个page的大小是8kb,数据文件是由page构成的。在同一个数据库上,每一个page都有一个唯一的资源标识,标识符由三部分组成:db_id,file_id,page_id,例如,15:1:8733,15是数据库的id,1是数据文件的id,8733是page的编号,page的编号从0依次递增。8个连续的page组成一个区(extent),数据文件中已分配(allocated)的空间被分割成区的整数倍。一次磁盘io操作作用于page级别,而空间分配的最小单元是区。

page是用于存储数据的,不同类型的page存储的数据是不同的,page的结构也是不同的。有些page是用于存储数据的,叫做data page,有些page是用于存储索引结构中的中间节点的,叫做index page,有些page是sql server存储引擎使用的,用于管理page的,叫做系统页。本文关注的是data page和index page,跟数据表有关。

日志文件没有page结构,它是由一系列的日志记录构成的。

一,page的结构

每一个page都由 头部(header),内容(content)和行偏移量(offset)组成,头部是在page的开始处,占用96bytes,用于存储page的编号,page的类型,分配单元(allocation unit)等系统信息。注:在单个page中最多存储8060bytes的数据。

the maximum amount of data and overhead that is contained in a single row on a page is 8,060 bytes (8 kb).

SQL Server Page结构深入分析

数据行存储在page header之后,数据行在page中的物理存储是无序的,行的逻辑顺序是由行偏移(row offset)确定的,行偏移存储在page的末尾,每一个行偏移是一个slot,占用2b。行偏移连续排列在page的末尾,称作槽数组(slot array)。行偏移以倒序方式存储行的偏移量,这意味着,从page末尾向page 开头计数,第一行的偏移量存储在page的末尾slot中,第二行的偏移量存储在page末尾的第二个slot中。

二,查看page头部信息

page头部信息存储的是page的系统信息,可以使用非正式的命令来查看:

dbcc page(['database name'|database id], file_id, page_number, print_option = [0|1|2|3] )

参数:file_id是数据库文件的id;page_number是page在当前文件中的编号;print_option是指打印信息的详细程度,默认值是0,只打印page header。

例如,查看资源标识符:15:1:8777733 page的头部信息:

dbcc traceon(3604)
dbcc page(15,1,8777733)

在我的数据库中,该page的头部信息(移除buffer的数据)如下所示,

page: (1:8777733)

page header:
page @0x0000005188b02000

m_pageid = (1:8777733)    m_headerversion = 1     m_type = 1
m_typeflagbits = 0x0    m_level = 0       m_flagbits = 0x220
m_objid (allocunitid.idobj) = 28503 m_indexid (allocunitid.idind) = 256 
metadata: allocunitid = 72057595905900544        
metadata: partitionid = 72057594059423744        metadata: indexid = 1
metadata: objectid = 1029578706  m_prevpage = (1:8777732)   m_nextpage = (1:8777734)
pminlen = 16      m_slotcnt = 2      m_freecnt = 4513
m_freedata = 3675     m_reservedcnt = 0     m_lsn = (1212327:16:558)
m_xactreserved = 0     m_xdesid = (0:799026688)   m_ghostreccnt = 0
m_tornbits = -1518328013   db frag id = 1      

allocation status
gam (1:8690944) = allocated   sgam (1:8690945) = not allocated 
pfs (1:8775480) = 0x40 allocated 0_pct_full       diff (1:8690950) = changed
ml (1:8690951) = not min_logged

page 头部中各个字段的含义:

1,page的编号

m_pageid = (1:8777733),该page所在的file id 和page id

2,page的类型

m_type = 1,page的类型,常见的类型是数据页和索引页:

1 – data page,用于表示:堆表或聚集索引的叶子节点
2 – index page,用于表示:聚集索引的中间节点或者非聚集索引中所有级别的节点
其他page类型(系统页是管理page的page,例如,gam,iam等)如下:

3 – text mix page,4 – text tree page,用于存储类型为文本的大对象数据
7 – sort page,用于存储排序操作的中间数据结果
8 – gam page,用于存储全局分配映射数据gam(global allocation map),每一个数据文件被分割成4gb的空间块(chunk),每一个chunk都对应一个gam数据页,gam数据页出现在数据文件特定的位置处,一个bit映射当前chunk中的一个区。
9 – sgam page,用于存储sgam页(shared gam)
10 – iam page,用于存储iam页(index allocation map)
11 – pfs page,用于存储pfs页(page free space)
13 – boot page,用于存储数据库的信息,只有一个page,page的标识符是:db_id:1:9,
15 – file header page,存储数据文件的数据,数据库的每一个文件都有一个,page的编号是0。
16 – diff map page,存储差异备份的映射,表示从上一次完整备份之后,该区的数据是否修改过。
17 – ml map page,表示从上一次备份之后,在大容量日志(bulk-logged)操作期间,该区的数据是否被修改过,this is what allows you to switch to bulk-logged mode for bulk-loads and index rebuilds without worrying about breaking a backup chain.
18 – a page that's be deallocated by dbcc checkdb during a repair operation.
19 – the temporary page that alter index … reorganize (or dbcc indexdefrag) uses when working on an index.
20 – a page pre-allocated as part of a bulk load operation, which will eventually be formatted as a ‘real' page.

3,page在索引中的级数

数据页在索引中的索引级数,m_level=0,表示处于leaf level。

对于堆表(heap),m_level=0表示的是data page;
对于聚集索引,m_level=0表示的是data page;
对于非聚集索引,m_level=0表示的是叶子节点

4, page的元数据

page的元数据十分重要,不仅能够查看处page所在的object,甚至能够查看该page所在的分配单元和分区id,在死锁进行故障排除时十分有用

metadata: allocunitid =72057595905900544,该page所在的分配单元id(allocation_unit_id)
metadata: partitionid =72057594059423744,该page所在的分区的分区id(partition_id)
metadata: indexid = 1,该page所在的索引id
metadata: objectid = 1029578706,用于表示page所属对象的object_id
5,page的链指针

由于数据表的page并不是单独存在的,而是通过双向链式结构连接在一起的,

m_prevpage = (1:8777732) :用于表示前一个page (fileid : pageid)
m_nextpage = (1:8777734)  :用于表示下一个page (fileid:pageid)

6, 其他头部字段

m_slotcnt = 2 :页面中slot的数量,用于page中存储的数据行数
m_freecnt = 4513  :页面中剩余的空间,单位是字节,还剩83字节的空间 
m_reservedcnt = 0 :为活动事务保留的存储空间,单位是字节
m_ghostreccnt = 0 :页面中存在的幽灵记录的总数(ghost record count)
关于page头部的信息,可以阅读《inside the storage engine: anatomy of a page》;

三,利用page的元数据排除死锁

page的元数据包含分区id,索引id和对象id,用户可以使用这些元数据,分析死锁产生的原因。系统追踪到产生死锁的资源,可能是一个page的资源标识符,如果能够确认发生死锁是由于数据表或索引的分区不合理导致的,那么可以重新设置分区列,或者设置分区边界值,把单个分区拆分成多个分区,这样就能把竞争的临界资源分配到不同的分区中,避免查询请求对资源的竞争,进而减少死锁的发生。

metadata: partitionid ,该page所在的分区的分区id(partition_id);
metadata: indexid ,该page所在索引id;
metadata: objectid,用于表示对象的object_id;