What You See Is What You Get Element

Oracle 数据库中的碎片管理和表收缩


作者:Liron Amitzi,Oracle Ace


简介

碎片是我们要尽量避免的常见问题。其表现形式多种多样、遍及多种组件,可能导致各种问题。在本文中,我将讨论导致空间浪费的表空间碎片。导致表空间碎片的原因很多,但我从未想到经常执行“shrink table”命令会这么快地导致这种碎片。

表收缩命令

在 Oracle 10gR1 之前,表的高水位线 (HWM) 向前移动(由于表中插入新行)之后就无法后退来减小表大小和回收空间,将空间退回给表空间的可用空间。我们从表中删除多行时,HWM 和表大小保持不变,减小表大小的唯一方法是截断表。Oracle 在 10gR1 中引入了一项激动人心的特性“shrink table”。

它是如何工作的?执行 shrink 命令时,Oracle 使用行移动(必须在表上启用)将行从表的最后数块移至表开头处。移动行之后,会发生表锁定,此时 Oracle 将 HWM 向后移动。然后,可以释放 HWM 之后的块,减小表大小。

从下面可以看出,在正常操作中,表中包括“已用块”(蓝色块)和“空块”(橙色块)。将行插入表中,而“已用块”中没有空间时,Oracle 会将 HWM(黑线)移向表末尾,将这些“空块”标记为“已用”。一旦没了可用块,就会再分配一个区。

frag-image1-jpg

 

现在让我们看看 shrink table 是如何工作的。在下图中,我们看到 HWM 位于表的末尾。假设我们删除了许多行,现在表块中有许多地方可容纳新行。shrink table 命令将这些行从表末尾移到靠近表开头部分的空闲位置(第一幅图)。然后,Oracle 可以将 HWM 移到表的最后一行,该行现在不位于表的末尾(第二幅图)。移动了 HWM 之后,HWM 以外的块被视为可用块(第三幅图),然后可将这些块从表中释放,退回给表空间(最后一幅图)。

frag-image2-jpg

本地管理的表空间

shrink table 命令只适用于本地管理的、支持自动段空间管理的表空间中的段。

使用本地管理的表空间时,可以通过两种方式配置区分配:

  • 系统分配 — Oracle 决定区大小,无论是何种用户配置。组成段的区一开始很小,随着段增大,Oracle 会分配越来越大的区。
  • 统一大小 — 表空间中的所有区大小相同,在创建表空间时配置。

我们来了解一下系统分配的工作原理:

frag-code1-jpg

上面的查询将显示各区及其大小。在本例中,它返回 202 行,总结如下:前 16 个区大小为 64KB(在我的数据库中是 8 个 8KB 块)。接下来的 63 个区大小为 1MB。在接下来的 120 区大小为 8MB。剩下来的 3 个区为 64MB。思路很容易理解。

Shrink Table 释放多少空间?

我们看到了 shrink table 的思路,我们明白这是一个非常有用的命令。现在我们将尝试了解有多少空间被退回给表空间。为此,我们将执行以下操作:

  • 1. 创建两个表空间,一个 (tbs_uni) 将采用统一的每区分配 1MB,另一个 (tbs_sys) 将采用系统区分配。
  • 2. 在每个表空间内创建一个表(使用相同的结构和数据)。
  • 3. 对两个表均启用行移动。
  • 4. 检查两个表的区。
  • 5. 从两个表删除行。
  • 6. 收缩两个表。
  • 7. 检查两个表的区,看看释放了多少空间。
  •  

    frag-code2-jpg

     

    frag-code3-jpg

    现在我们将使用先前用过的查询来获取区分配:

     

    frag-code4-jpg

    与前面一样,我不在此出贴出完整输出,只贴出摘要:

    • 对于统一分配表空间中的表 TAB_UNI,我们看到 113 个区,每个区 1MB。
    • 对于系统分配表空间中的表 TAB_SYS,我们看到 86 个区。和预料的一样,前 16 个区为 64KB,接下来的 63 个区为 1MB,最后 7 个区为 8MB。

    现在,我们将从这两个表删除数行并收缩表,然后再来看区图。

    frag-code5-jpg

    现在区图显示:

    • 对于表 TAB_UNI,我们看到 111 个区,每个 1MB。比以前少两个区。
    • 对于表 TAB_SYS,我们看到 85 个区。比以前少一个区。前 84 个区和以前完全一样,最后一个区减小至略大于 6MB。这是合理的,因为区会随时间增大,我们可以分配任意大小的区。
    • 最后需要将 2500 行重新添加到这两个表:

       

      frag-code6-jpg

      现在区图显示:

      • 对于表 TAB_UNI,我们看到 113 个区,每个 1MB。与开始时完全一样。
      • 对于表 TAB_SYS,我们又看到了 86 个区。但前 84 个区与先前完全一样,新的一个区(第 86 区)是 8MB,但第 85 区仍是略大于 6MB,并未再次增大至 8MB。

      那么,问题出在哪里?

      问题在于某些我们没有检查或考虑到的东西。要找到问题所在,我们需要在区图中添加区的物理位置。文件中区的位置依 DBA_EXTENTS 表中的 block_id 列而定。block_id 列代表区中第一块的 id,其中 1 是文件开头。

      为简单起见,我将使用相关表空间中的一个文件。我们将使用以下查询查看包含区位置的区全图:

       

      frag-code7-jpg

      此查询的结果将包括相关文件的 file_id、区信息(block_id 和大小)及内容(段名或“free space”)。

      我不打算贴出整个结果集。以下是部分 TBS_UNI 结果:

       

      frag-code8-jpg


      注意 6MB 区之后的可用空间。记住带我们来到此情形的流程。我们填充了表,然后删除了数行,收缩表,再插入更多行。开始时,表的最后一个区大小为 8MB;收缩表后,区缩小到约 6MB,将约 2MB 退回给表空间。插入新行时,Oracle 需要为其分配新区;但似乎这些区不能起始于文件中的任意块。在本例中,新的 8MB 区不能起始于块 14224(该块距文件开头 111.125MB),而是起始于块 14336,该块恰巧距文件开头 112MB。

      由于此表空间使用系统分配区管理,其他需要小区的表可以分配此可用空间。但大表不能分配此空间,分配下一区时将留下未用区域。

      总结一下这个问题,我们在系统分配区管理表空间收缩表时,会将表末尾的一部分退回给表空间的可用空间。通常,这样做留下的区小于表中通常的最后一个区。表的下一个区并不会总是能够准确地起始于下一块,而是取决于所请求区的大小。如果发生这种情况,各区之间剩下的空间可能只够分配小区,因此通常不能用于同一表或其他大表的区。

      现在要讨论两个问题,一个是如何整理表空间碎片,另一个是如何避免碎片。

      整理表空间碎片

      如果表空间出现碎片,为了消除碎片,我们将不得不丢弃“有问题”的区。问题是通常我们不能这么做,因为表中有太多数据,区太靠近表的开头位置,不能取消分配。我能想到的唯一的解决办法是使用“alter table … move”命令或“insert … select”重新创建表。重新创建表时,会分配一个包含数个新区的新段,释放旧段。丢弃旧段之后,碎片不再存在,但这是一项麻烦的操作,需要计划和停机。

      避免碎片

      最好的办法是完全避免此问题。这取决于您的具体系统以及您如何使用数据库。以下几点可能值得考虑:

      • 如果您对大表使用系统分配表空间,对这些表进行删除和插入操作,不应考虑使用“shrink table”特性。这样,新行可以使用被删除的行留下的空间,而不会出现任何问题或碎片。
      • 如果您在小表上使用“shrink table”,可能不会有问题。
      • 如果对大表使用“shrink table”,但不经常使用,同时还为各种小表分配此表空间上的新区,碎片问题可能不大。偶尔的“shrink table”操作造成的少量可用空间将由小表使用。
      • 如果系统分配表空间上只有大表且须定期收缩表,可考虑转为统一分配,采用足够的区大小来避免碎片问题。

      总结

      “shrink table”命令在许多情况下都非常有用。但在有些用例中,可能会发生严重的碎片。处理这种碎片很困难,因此最好事先考虑好避免出现碎片。

      在本文中,我为您提供了一个我遇到的实际例子,帮助您了解和预防这种碎片问题。希望这对您有所帮助。



      关于作者

      Liron 是一位拥有逾 11 年经验的高级 DBA。这些年里,Liron 担任过许多各领域公司的高级顾问并管理 Oracle 咨询团队。他主要擅长高可用性解决方案、性能、备份和恢复及其他基础架构和应用数据库领域。他还是教授 Oracle 课程的知名高级讲师,并在各种活动和论坛发表演讲。Liron 目前是一家领先的以色列 Oracle 咨询公司 Brillix 的专业服务副总裁。