存储引擎内幕:IAM页、IAM链及分配单元

 http://www.windbi.com/showtopic-1682.aspx

存储引擎内幕:IAM页、IAM链及分配单元

存储引擎内幕:IAM页、IAM链及分配单元


--王成辉翻译整理,转贴请注明出自微软BI开拓者 www.windbi.com
-- 原帖地址

以前写过一系列使用 DBCC Page 的贴子,这是接下来的另一篇。

IAM

一个 IAM 页(索引分配页)跟踪单个文件里将近 4GB 的空间,以 4GB 为界。这 4GB 的数据称为“ GAM 间隔”。一个 IAM 页跟踪属于单个实体(这里我小心的选择我的用语,并且不使用 SQL Server 有诸如“对象”等之类隐含意义的词语)的特定 GAM 间隔内的扩展盘区。

一个 IAM 只跟踪单个文件里单个 GAM 的空间,所以如果数据库有多个文件、或者一些文件不止 4GB ,并且实体从多个文件或单个文件的多个 GAM 间隔分配空间的话,那么你可以看到每个实体要跟踪它使用所有空间需要多少 IAM 页。如果一个实体需要多个 IAM 页来跟踪所有扩展盘区的话,那么这些 IAM 必须链接在一起。这就是所说的 IAM 链。更多内容请往下读。


每个 IAM 页有 2 条记录,一个 IAM 页头和位图。让我们用 DBCC Page 来看看,我使用 这个帖子里 的数据库。在我们创建的表上执行 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
    • 槽数为2——一个是IAM页头记录,另一个是位图自身
      • 页几乎全部填满

IAM 页头有下面的字段:
    • sequenceNumber
      • 这是IAM链里IAM页的位置。对于每个添加到IAM链里的页会按1增加。
    • status
      • 这个未使用。
    • objectId
    • indexId
      • SQLServer2000及以前版本里,这些是包含IAM页的对象和索引ID。在SQLServer2005及以后的版本里未使用。
    • page_count
      • 这个未使用——它用来记录单个页分配数组里被跟踪的页ID的数量。
    • start_pg
      • 这是页映射的GAM间隔,它存储了映射间隔里的第一页的ID
      • Single Page Allocations array
        • 这些是从混合扩展盘区已分配的页。该数组仅用于链里第一个IAM页(整个IAM链只需跟踪最多8个单个页的分配)。

位图占用了 IAM 页余下的空间,并且在 GAM 间隔里每个扩展盘区都有一位。如果扩展盘区被分配给实体,位就被设置,如果没有,就清除。显然,对于映射不同实体相同 GAM 间隔的 2 IAM 页不可能有相同的位设置—— DBCC CHECKDB 会检测这个。在上面 DBCCPAGE 的输出结果中,你可以看到没有扩展盘区分配给表。你会注意到输出结果仅从文件里的 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


你可以看到整个单页分配数组(
single-page allocation array )都填满了,然后分配切换到统一扩展盘区。第一个可用的扩展盘区一定是从 160 页开始的,并且从开始到 296 页所有的扩展盘区现在已经分配。也要注意文件肯定已经增长了,因为输出结果现在达到文件里的 400 页了。

IAM 页需要注意的两点:
    • 它们自身有来自混合扩展盘区里的单页分配,并且任何地方都不记录。
      • 它们可以从任何文件里分配来跟踪任何其它文件里的扩展盘区。

IAM

如果我们继续增长文件并填充表,那么最终我们需要另一个 IAM 页来映射下一个 GAM 间隔。这样 IAM 链就产生了。它是跟踪单个实体空间分配的 IAM 页的链接列表。这个链接列表根本不会存储—— IAM 按照它们需要的顺序增加。再说一次,列表内的 IAM 页按照它们添加到列表内的顺序进行编号。

实体的定义——是什么在使用 IAM 链?这在 SQL2000 2005 中有很大的不同。

SQL Server2000 中,单个 IAM 链用于以下情况:
    • 堆或聚集索引
      • 一个表只可能是堆表或聚集表(有聚集索引的表),不可能二者都是,对应的索引ID分别为01
    • 非聚集索引
      • 索引ID2250(即你只可能有249个非聚集索引)
      • 表的完整的大对象存储
        • 针对堆表或聚集表里的大对象的列(textntextimage)。有时候称作“文本索引”并且有一个固定的索引ID,即255
这在 SQL Server2000 及以前版本里就决定了每个对象最多有 251 IAM 链。在 SQL2000 里通常我概括说,每个索引有一个 IAM 链(这是挺合适的,如果你记住 IAM 代表索引分配映射的话)。

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

SQL Server 2005 以及后续版本里,有了很多变化。 IAM 链和 IAM 页除了它们代表的意思不同外,其余完全相同。表现在可以有高达 750000 IAM 链! IAM 链现在针对 3 种对象来映射空间分配:
    • 堆和B树(B树是用于存储索引的内部结构)
    • LOB数据
      • 行溢出数据

我们现在称这些跟踪的空间分配的单元为分配单元( allocation units )。分配单元的这 3 种类型的对应的内部名称分别为:
    • hobtHeapB-Tree,即堆或B树)分配单元
    • LOB分配单元
      • SLOB分配单元(Small-LOBShort-LOB,即小LOB或短LOB

对应的外部名称分别为:

    • IN_ROW_DATA分配单元
    • LOB_DATA分配单元
      • ROW_OVERFLOW_DATA分配单元

它们真的不可能继续称为 IAM 链了,因为它们不再跟踪索引的空间分配了。然而,它们是 IAM 页的链仍然被称作 IAM 链,而且跟踪的单元现在称为分配单元。抛开这点来说,就没什么不同了。

让我们快速的来看看 SQL Server 2005 里这 3 个新的特点,这些改变是必要的,并且也提升了每个表潜在 IAM 链的数量。

包含列

非聚集索引有能力在其叶级包含非键列:

    • 它允许非聚集索引真正覆盖那些查询结果包括多于16列或者查询结果里列长度的总和大于900字节(记住非聚集索引受限于16列和900字节)
    • 它允许包含那些由于数据类型(例如:varchar(max)XML)的原因而在非聚集索引里不可能作为索引键的一部分的列
      • 它允许非聚集索引覆盖查询而不必把所有列都包含在索引键中。因为索引键会包含在B树所有层的记录里,所以这让索引变得更小。

节省空间的一个例子:考虑一个有一亿行的索引,键长为 900 字节,但仅开头 2 个整型键真正是索引键所需要的,其它 4 个固定长度列可以作为包含列存储在索引里。对于 900 字节的键,每个数据库页可以容纳 8 行()。这意味着在叶级将会有 12500000 个页, B 树里的上一层有 1562500 个页,以此类推,得到的总数为 12500000 1562500 195313 24415 3052 382 48 6 1 14285717 页(即有 1785717 存储在 B 树叶级的较上层)。

如果我们使用包含列的方式,那么键的尺寸缩小到 8 字节,而且对于行的开销而言,在 B 树最高层的行长度降到 15 个字节。注意在叶级的扇出仍然会是 8 ,因为叶级每行存储的数据的量是相同的。所以这意味着在叶级会有 12500000 页,紧接的上一层有 23278 页,依此类推,总共有 12500000+23278+44+1 12523323 页(其中 23323 页用来存储 B 树叶级以上的页)。和 900 字节键的全部大小相比,这节约了 12 %即 1762394 页,大约 13.6G !显然,这有点勉强,但你可以看到空间节省是怎样发生的。

添加这个特点的主要原因是为了使覆盖查询真的能起作用。覆盖查询是查询优化器知道它能从非聚集索引里得到所有查询结果的查询,而且不必在基表里查找数据而增加额外的 IO 就能满足查询——显著的性能提升。

现在非聚集索引可以有包含列,而且这些列可以是 LOB 数据类型(但仅限于 SQL2005 新的数据类型如 varchar(max) nvarchar(max) varbinary(max) XML )。这意味着不再可能有单个的 LOB 分配单元了(在 SQL2000 里是单个文本索引的情形),因为每个索引都可以有它自己的 LOB 集。现在你可能会问对于提及的各个索引以及基表为什么不只是单个 LOB 集呢。只是我们考虑在 SQL2005 的开发期间这会变得更加复杂。
这样,对于新的特点而言,每个索引需要 2 个分配单元——一个针对数据或索引记录( hobt 分配单元)、另一个针对 LOB 数据。

很大的行


困扰架构设计者很长时间的一件事情就是表的行大小受
8060 的限制,而这个限制在 SQL2005 里解决了。方法是允许可变长的列(例如 varchar sqlvariant )在行尺寸太大以至于单个页内不能存储时把它推到行外。

但这些被推出去的列值存在哪儿呢?它们被有效地转为小 LOB 列。行内的列值用一个 16 字节的指针来代替被推出去的行外列值,它的 LOB 值存储在独立的分配单元里——行溢出(或 SLOB )分配单元。这些值仅使用独立的分配单元以和常规 LOB 值一样的方式来存储在 text 页里。 SLOB 分配单元仅在第一个列值被推出行外的时候创建。

这个特点也适合于非聚集索引——如果你考虑非聚集索引有包含列的能力的话,那么就可以很容易的让非聚集索引的行在一个页里存不下。为了除去 900 字节的限制,通过不扩展非聚集索引的行溢出特征来用 8060 字节的限制来代替它,也是很短视的。

现在,该新特点的另外一点,每个索引最多可以有 3 个分配单元—— hobt LOB SLOB 。这样的话,每个表最多仅可以有 750 IAM 链(记住 IAM 链现在映射分配单元的存储分配,所以 250 个索引× 3 个分配单元= 750 IAM 链)。但我早先说了每个表有 750000 IAM 链——其余的来自哪里呢?

分区

这为我们提供了 1000 倍的 IAM 链。正如我们已经知道的,分区是一个新的特点,它允许表和索引按范围分成一系列的部分,每部分单独存储(最通常的情况是单独的文件组)。分区以后作为一个单独的主题来讨论。

如果表或索引的每个范围或分区分区单独存储的话,那么各自都需要自己的 hobt 分配单元。当然,由于每个分区相连的 LOB 值需要存储,素衣所以每个分区也需要 LOB 分配单元。同时,行溢出特点是针对行的,所以每个分区里的行会溢出到 SLOB 分配单元里,就像非分区表和索引一样。这样表或索引的每个分区可能最多有 3 个分配单元(因而有 3 IAM 链)。

然而,这个 1000 倍来自于哪里呢?每个表或索引最多可以有 1000 个分区。这给了我们 250 个索引× 1000 个分区× 3 个分配单元= 750000 IAM 链。实际上这可能不会发生,但它是有可能的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值