存储引擎揭秘:基本结构之四——IAM页,IAM链和存储单元

来源:互联网 发布:红色警戒 for mac 编辑:程序博客网 时间:2024/05/19 05:31

 

存储引擎揭秘:基本结构之四——IAM,IAM链和存储单元


原文地址:

http://www.sqlskills.com/BLOGS/PAUL/post/Inside-the-Storage-Engine-IAM-pages-IAM-chains-and-allocation-units.aspx

 

译者:正如本文作者所言,本文是以前他的一些文章的结合,以前曾经翻译的相关文章有:

1 SQL Server2000中的IAM链》

原文地址http://blogs.msdn.com/b/sqlserverstorageengine/archive/2006/06/24/645803.aspx

译文地址http://blog.csdn.net/misterliwei/archive/2010/09/25/5906035.aspx

2SQL Server2005中的IAM链》

原文地址

http://blogs.msdn.com/b/sqlserverstorageengine/archive/2006/06/25/under-the-covers-iam-chains-and-allocation-units-in-sql-server-2005.aspx

译文地址http://blog.csdn.net/misterliwei/archive/2010/09/26/5907155.aspx

 

正文:

本文是我以前发表的材料的组合,新加入一些DBCC PAGE输出。

IAM

IAMIndexAllocation Map,索引分配映射)页用来跟踪单个文件中约4G大小的空间,跟踪的空间是按4G字节对齐。被跟踪的这4G大小的页被称为“GAM区间GAM Interval)。IAM页所跟踪的GAM区间中的空间是属于同一实体的(这里,我选择的是实体而没有使用SQL Server中的对象)。

因为IAM只是跟踪单个文件里的一个GAM区间里的空间。若一个数据库包括多个文件,或者一些文件大小超过4G,并且实体分配的空间恰好跨越多个文件或跨越一个文件的多个GAM区间,那么你就会看到为了跟踪单个实体的所有空间是如何使用多个IAM页的了。如果一个实体需要多个IAM页来跟踪它的所有的区,那么所有的IAM页必须链在一起,这就是IAM链的由来。更多内容见下。

每个IAM页有两条记录:一个IAM页头和一个位图。让我们用DBCC PAGE来看1IAM页。我用了以前《page split》文章用的数据库。使用DBCC IND查看我们创建的表:


通过查看PageType列,我们可以看到有一个IAM页(页类型为10)的ID为(1:152):

DBCC TRACEON (3604);

GO

DBCC PAGE ('pagesplittest', 1, 152, 3);

GO

m_pageId =(1:152)         m_headerVersion =1                 m_type = 10
m_typeFlagBits = 0x0    m_level =0                         m_flagBits = 0x200
m_objId (AllocUnitId.idObj) = 68     m_indexId(AllocUnitId.idInd) = 256
Metadata: AllocUnitId = 72057594042384384
Metadata: PartitionId =72057594038386688        Metadata: IndexId =1
Metadata: ObjectId = 2073058421  m_prevPage =(0:0)        m_nextPage = (0:0)
pminlen = 90      m_slotCnt = 2              m_freeCnt = 6
m_freeData = 8182    m_reservedCnt =0         m_lsn = (18:116:13)
m_xactReserved = 0    m_xdesId =(0:0)        m_ghostRecCnt = 0
m_tornBits = -1947725876

Allocation Status

GAM (1:2) =ALLOCATED               SGAM (1:3) = ALLOCATED
PFS (1:1) = 0x70 IAM_PG MIXED_EXT ALLOCATED  0_PCT_FULL    DIFF (1:6) = CHANGED
ML (1:7) = NOT MIN_LOGGED

IAM: Header @0x620CC064 Slot 0, Offset 96

sequenceNumber = 0   status =0x0            objectId= 0
indexId = 0     page_count =0             start_pg= (1:0)

IAM: Single Page Allocations@0x620CC08E

Slot 0 =(1:143)          Slot 1 =(1:153)                    Slot 2 = (1:154)
Slot 3 =(0:0)            Slot 4=(0:0)                      Slot 5 = (0:0)
Slot 6 =(0:0)            Slot 7= (0:0)

IAM: Extent Alloc Status Slot 1@0x620CC0C2

(1:0)        -(1:272)      = NOT ALLOCATED

关于页头部需要注意:

  • 正如我们所期望的——页类型为10,。
  • 前页和后页指针都为NULL,因为在IAM链中没有其他的IAM页。
  • slot数目为2:一个是IAM头记录;另一个是位图。
  • 页几乎满了

IAM页头有下面的字段:

  • sequenceNumber
    • 这是IAM页在IAM链中的位置值。每一个IAM页加入到IAM链时该值增加1
  • status
    • 未用
  • objectId
  • indexId
    • SQL SERVER 2000及以前版本,这是IAM页所属的对象和索引的ID2005和以后的版本这两个字段未用。
  • page_count
    • 字段未用——原来是用来跟踪单页分配数组中页数目。
  • start_pg
    • IAM页映射了一个GAM区间。这个字段存储了映射区间的首页ID.
  • 单页分配数组(Single Page Allocations array
    • 这些是从混合分区中分配的页的数组。这个数组只存在IAM链中的第一个页中(因为整个IAM链中只需要跟踪8个单独分配的页)。

位图占用IAM页剩下的空间,每一位表示GAM区间中的每一个区。如果区被分配给该实体,那么对应位就置1,否则为0。很明显,为不同实体映射同一GAM区域的两个IAM页不可能有相同的位被置上——DBCC CHECKDB会检查这个。在上面的DBCC PAGE输出中,你可以看出没有区分配给表。你会发现输出最多到272页所在的区——这是因为数据文件就这么大。我往表中插入更多的行,然后再次对IAM页执行DBCC PAGE,这次DBCCPAGE输出如下:

IAM: Single Page Allocations@0x620CC08E

Slot 0 =(1:143)          Slot 1 =(1:153)                    Slot 2 = (1:154)
Slot 3 = (1:155)          Slot 4 =(1:156)                    Slot 5 = (1:157)
Slot 6 = (1:158)          Slot 7 =(1:159)


IAM:Extent Alloc Status Slot 1 @0x620CC0C2

(1:0)       - (1:152)      = NOT ALLOCATED
(1:160)      - (1:296)     =     ALLOCATED
(1:304)      - (1:400)      =NOT ALLOCATED

你会发现所有的单页分配数组都已经满了然后转向分配统一区。第一个有效的区开始于160页,一直到296页开始的区。同样注意到文件一定是变大了,因为现在输出显示文件中已经有400页了。

关于IAM页还要注意两件事:

  • IAM页是从混合区中分配而来的,且这些页不受监控。
  • 一个文件中分配的IAM也可以用来跟踪另一个文件的区。

IAM

如果我们一直增大文件并往表中插入数据,最终我们将需要另一个IAM页来映射下一个GAM区间。这就是IAM链的由来。IAM链表用来跟踪单个实体上的空间分配。这个链表是不排序的——IAM页按添加的顺序加入链表中,每个IAM页有一个数值,同样是以添加顺序增加的。

“实体”的定义。到底是谁使用IAM链?这个概念在SQL SERVER 20002005中区别很大。

SQL Server2000中,下面每个实体都有一个IAM链表:

  • 堆或聚集索引
    • 一个表只能选其一,不能两者皆有。它们的索引ID分别为0 1
  • 非聚集索引
    • 它的索引ID2250(就是说只有249个索引)。
  • 表的完整LOB存储
    • 对于堆或者聚集索引中的LOB列,有时也被称为文本索引,它拥有一个固定的索引ID255

SQL SERVER 2000及以前版本中每个兑现最多251链表。我常总结为:在SQL SERVER 2000中,每一个索引一个IAM链(如果你还记得IAM索引分配映射的话,我觉得还是很贴切的)。

分配单元(SQL SERVER 2005及以后版本)

现在在SQL SERVER 2005及以后版本中,发生了一些变化。虽然IAM链和IAM页与以前是一模一样的,但是它们所对应的东西变了,而且现在一个表可以拥有750000IAM链!现在IAM链为三类东西映射分配空间:
1
.堆和B树(B树是系统用来存储索引的内部结构)
2
LOB数据
3
.行溢出数据

我们称这些分配空间的单元为分配单元(allocation units),这三类分配单元的相应的内部名称为:
1
HOBT分配单元(发音和指环王中的霍比特人一样)
2
LOB分配单元
3
SLOB分配单元(SMALL –LOB)
对应的外部名称为:
1
IN_ROW_DATA分配单元
2
LOB_DATA分配单元
3
ROW_OVERFLOW_DATA分配单元

严格来说,它们不能再被称为IAM链了,因为它们不再跟踪索引的分配空间了。只是它们还是IAM页的链表,所以还被称为IAM链,现在它跟踪的单元叫分配单元(allocation unit)。除了这些,和以前没有任何区别。

让我们来看看引起变化的SQL SERVER 2005的新增的3个特性,这些特性增加了每个表的IAM链数目的潜力。

1.包含列
这项功能可以在非聚集索引的叶节点中包含非键列。这条特性因为下面三个原因所以非常有用:
1
).当查询结果包含超过16列或者所有列的总长度大于900字节时,它允许一个非聚集索引真正地覆盖一个查询(还记得吗?在SQL SERVER中,一个非聚集索引键不能超过16列且不能超过900个字节。)。
2
).它允许不能作为索引键的数据类型包含在非聚集索引(比如text或者XML类型)中。
3
).它允许一个非聚集索引覆盖一个查询而又不需要所有的查询列都作为索引键列。因为索引键会包含在B树的所有层的行中,所以包含列可以使得索引占用的空间更小。

举个节省空间的例子:假设有一个1亿行的索引,其键长度为900字节,但是实际上只有前面2个整数需作为索引键,其它4个固定列可以作为包含列。一个900字节的索引,那么1页只能包含8行(比如,fanout8)。这就是说叶节点需要12500000页,上一层为1562500……做个汇 总,一共需要12500000+ 1562500 + 195313 + 24415 + 3052 + 382 + 48 + 6 + 1 = 14285717页(包括叶节点以上层的1785717页)。
如果我们使用包含列的方法使得键缩为8个字节,那么B树的叶节点以上层一行大小为15字节(包含了一些行的负载,这样fanout约为537)。注意叶节 点的fanout还是8,因为存储在叶节点上的数据是一样的。这样还是12500000页叶节点,但是上一层结点为23278页。所以总的 为:12500000 + 23278 + 44 + 1 = 12523323页(包括叶节点以上层的23323页)。和上面的900字节的键比较,这节省了1762394页(12%)或者13.6GB。当然这个例子有点夸张,但是节省空间是显而易见的。

增加包含列这种特性的主要原始是可以真正地覆盖查询。一个覆盖查询是指查询优化器知道从一个非聚集索引中得到所有查询结果,所以就没有必要使用额外的IO从基表中查询数据就能满足查询,这是非常重大的性能节省。

现在非聚集索引有了包含列,这些列可以是LOB数据类型。这就是说SQLSERVER 2005再也没有必要有一个单独的LOB分配单元(在SQL SERVER 2000中有一个单独的text索引)了,因为每个索引都有自己的LOB组了。你可能会问我们为什么没有增加单独一组LOB,然后让各个索引和基表指向这些LOB列?我们确实曾经考虑过,但发现它会使问题更加复杂。

所以,有了这个特性,每个索引需要两个分配单元——一个是为数据或索引(HOBT分配单元),一个为任意的LOB数据。

2
.巨行(Large Rows
一个一直折磨架构设计师的问题是表的行大小的8060字节限制。在SQL SERVER 2005中,我们去除了这个限制。我们解决这个问题的方法是当行的长度太长以至于不能放在一个单独的页中,允许系统把变化长度列(如varchar, sqlvariant)挤出行去。

那么这些列的值被挤到什么地方去呢?我们有效地将它转换成小的LOB列。行中列值由一个指向挤出列值的16字节指针所代替,挤出列就好像是一个LOB值被 存储在一个独立的分配单元——行溢出分配单元(SLOB)中。这些值和正规的LOB值一样存储在text页中,只不过用的是一个独立的分配单元,只要当行 中有一列被挤出时就会创建SLOB分配单元。

这种巨行特性同样适用于非聚集索引。如果你考虑在非聚集索引中使用包含列,那么你的非聚集索引很容易超过一个页的大小。如果不在非聚集索引上使用行溢出特性,那么我们将刚摆脱了900字节的限制,又会有8060字节的限制了。

现在有了这些特性,每个索引能有三个分配单元——HOBTLOBSLOB。即使这样,一个表最多也就是有750IAM链啊(记住IAM链现在用来映射分配单元了,所以250个索引*3个分配单元=750IAM链)。但是我前面提到每个表有750000IAM——剩下的是从哪儿来啊?

3
.分区
分区给了我们1000倍的能力。可能你早就知道了,SQLSERVER 2005中新增的分区特性使表和索引能被分割成一系列的段,每个段被单独存储(更常见的是被存储在单独的文件组中)。分区需要另一文介绍。
如果表或索引的每个段或分区是单独存储的,那么每个存储就需要它自己的HOBT分配单元。当然,每个分区可以存储LOB值,所以每个分区需要一个LOB分 配单元。还有每个行的行溢出特性,就像未分区时表和索引一样,每个分区中的行会溢出至SLOB分配单元中。所以每个表或索引的分区都能有3个分配单元(, 也就有了3IAM链)。
那么1000倍是从哪儿来的呢?这是因为每个表或索引可以有1000个分区。就是250索引*1000分区*3个分配单元=750000IAM链。现实中这可能并不会发生,这只是一种可能性。

原创粉丝点击