Oracle压缩黑科技(三)| OLTP压缩

在本文中,我们将体验“OLTP”压缩(最初称为“compress for all operations”)。我们将重新运行一些测试,看看Oracle在这个特性中有什么不同表现。

原文链接:https://www.red-gate.com/simple-talk/sql/oracle/compression-in-oracle-part-3-oltp-compression/
译者 周天鹏

在这个系列的第二部分文章中,我们探讨了当使用基础压缩时,update操作对于压缩数据的影响。同时了解到Oracle在update操作之前的“解压”操作会导致已被高度压缩的行变得非常大,即使是少量的更改也会造成大量的行迁移。而另一方面,我们看到Oracle也极力将解压比例降至最低——通过只扩展那些包含正在更新的列的标记。

甚至还有一种特殊情况,即“无更改”的update操作不会进行任何解压,不过Randolf Geist在上一篇文章中的一条评论里提出,这个特殊的情况直到11.2.0.3才实现;“DBA”的后续工作表明,如果您在一个块中更新大量的行,这种特殊的情况可能会失效。

我们还看到Oracle没有尝试在更新后“重新压缩”行,即使存在着其他可以用来减小行大小的标记。

在本文中,我们将体验“OLTP”压缩(最初称为“compress for all operations”)。我们将重新运行我们的一些测试,看看Oracle在这个特性中有什么不同表现。在测试中,我将使用11.2.0.3,本地管理的表空间,1m大小的extents,以及(我稍后将提到的原因)freelist管理,而不是自动的段空间管理(ASSM)。

| PCTFREE

我们在第一个基本压缩测试中定义一个压缩表,用来查看初始加载的数据的状态。 我们将再次做同样的事情,但是使用选项“compress for OLTP”(或者对于那些使用稍旧版本的Oracle的人来说可以称作“compress for all operations”)。 我们首先创建一个50000行数据(来自视图 all_objects)的表,然后检查不同的操作是否产生不同的结果。 我在原文中操作的步骤是:

  • Create table as select from all_objects

  • Create table with compression as select from all_objects

  • Create empty table with compression enabled, insert rows

  • Create empty table with compression enabled, insert rows with append hint

  • Create table (no compression) as select; enable compression; move table

为此,我做了另一个测试,创建表,然后一次插入一行,由pl/sql循环提交。在每次测试之后,我们通过查看user_tables获得的结果如下:

我们可以看到的最明显的一点是,对于OLTP压缩pctfree默认值与非压缩表相同都是10(这就解释了为什么在很多情况下,我们看到的是211块,而不是我们看到的189块基本压缩的块);我们还可以看到,即使是正常的插入也会导致数据被压缩——回想一下,对于基本压缩,我们不得不使用直接路径加载;然而,我们也注意到,我们使用普通插入的压缩并不像直接加载的压缩那样好。同样值得提醒的是,仅仅启用压缩不会改变现有数据(test 5a),我们必须重新创建表来压缩数据。单行插入操作产生的结果与数组插入级别的压缩非常接近,因此在后续的测试中我没有采用这种方法。

在11g中有多种关于压缩的统计信息,可以让我们了解在某种类型的批处理过程中正在发生什么(或已经发生了什么),我在进行简单的插入测试(number 3)时,对会话的统计信息进行了快照,并得到了以下重要结果:

其中一些数字很容易解释——我们的最终表有227个块,这就是所谓的“HSC OLTP Compressed Blocks”;(这是做一个合理的猜测,HSC是“heap segment compression”);如果我们没有使用压缩,那么这个表将会是712个块,那么我们粗略的计算一下,在允许pctfree的情况下,节约了3.5m的空间。

然后我们必须解释“heap block compress”和“HSC OLTP inline compression”的表现,这两个记录值都是1521。“heap block compress” 第一次出现在10 g,而且当初与压缩无关,它只计算一个块被“整理”的次数,通过把所有的行块都推到块的底部,从而得到可用的空闲空间。

如果您从一个块中删除了一些行或者更新了一些行,因此它们变得更长而且会被移动到free space gap。从而行堆中就留下了空缺。如果oracle想要更多连续的空闲空间,它会去重新排列块的内容,将行向下移动到块的末尾(调整行目录),以便得到一整块的空闲空间。 这种操作被称为“heap block compress”。这也解释了为什么块转储显示了两种可用空间的度量,“tosp"(total space free in block)和“avsp”(available space in the free space gap)——忽略一些异常,tosp是avsp加上所有碎片空间的总和。

(可以参考这篇文章:
jonathanlewis.wordpress.com/2010/03/30/heap-block-compress);

因此,我们的大量插入导致了Oracle对一个块进行了1521次的清理——由于我们总共只有227个块,所以这是搞清楚OLTP压缩是如何工作的一个重要线索。有(或至少)两种机制来考虑——插入行和更新行,这样它们的大小就会增加。

插入行时,Oracle直到将块的空间使用到超过pctfree限制时,才会压缩它们; 此时Oracle会“暂停”运行以对当前块中的数据运行其压缩算法(记录第二个统计“HSC OLTP inline compression”)。 运气好的话,这将减少数据量,留下足够低于pctfree标记的空间来插入新行。 (注意:现有数据在插入新行之前被压缩,新的行将不会被压缩,除非另一个会导致块超过限制的插入触发了压缩。)这就是我们在统计中看到的:我们得到一些块,然后压缩它,再添加一些,然后重新压缩它,再添加一些,一直重复。

我们可能会认为更新的机制原则上应该是相似的,尽管它的目标有所不同。SQL参考手册(E10592-04 p16-34)“Create Table”里告诉我们:“通过指定COMPRESS FOR OLTP可以启用OLTP表压缩。 Oracle数据库在表上的所有DML操作过程中都会压缩数据”。接下来的问题是什么触发了对更新(或删除)的压缩。 答案似乎没有 - 看起来好像OLTP压缩是在要超过pctfree阈值的插入时触发的。

(Randolf Geist在他的博客上也注意到了这个观察:
oracle-andolf.blogspot.co.uk/2011/05/assm-bug-reprise-part-2)。

我为触发压缩而进行了的各种update尝试,最后一个测试是创建一个块有两行数据,每行有100个‘Y’(形成了一个标记)组成,以及10行由60个‘X’( 形成了另一个标记)和各种其他行来达到pctfree极限。 为了测试几个选项,仔细计算了列的行数和列的大小。然后,我尝试了以下方法——为每个测试重新创建数据:

  • 将所有包含X的行更新为Y

  • 更新包含X行中的9行,提交,更新最后一个X行

  • 更新包含X行中的9行,提交,删除100个“备用”行,提交,更新最后一个X行

在前两种情况下,第十个“X”行在更新时已经迁移 —— Oracle没有重新压缩以节省空间,即使有一个合适的标记可以重新使用。 在最后一个例子中,发生了一个“heap block compress”,整理了这个块,这样就会有一个连续的空闲空间,oracle就可以将它用于更新的行,但是不会再次压缩。

可悲的是,“OLTP压缩”(以前叫“compress for all operations”)似乎并不压缩所有的操作,它只压缩插入,对比基本压缩,它的好处是:

  • 它留下10%的块空间可用于更新

  • 不需要直接路径插入来触发压缩。

鉴于它的工作方式的限制,你可能会发现它带来的问题可能会是你想避免的东西。

| Problems

正如我们所看到的,针对OLTP的压缩不适用于除插入之外的任何操作。但即使如此,它似乎并没有有效的工作。一个简单的例子“insert as select”产生了一个有227个块的表,而我们使用直接路径插入的时候是211个块。当我dump表的前几个块时,我发现每块中的最后7或8行没有被压缩,块中的空闲空间实际上大于pctfree指示的10%,它并未有我们想象的那样压缩那么多。 有可能是Oracle有一个算法,说“如果我重新压缩块节省空间可能小于x%,就不会这样做”;你可以想象,当你有未压缩的8行数据在一个持有240行的块中,那么通过压缩获得的额外空间可能看起来很小,特别是在考虑应用压缩算法所需的CPU时间时。

但是,您可能还记得,我开始写这篇文章的时候使用了freelist管理,而不是ASSM,当我使用ASSM重复简单的“插入50,000行数据”时,结果表的大小从227块跳到了250块。在这250个块中,看起来33个块根本就没有施加压缩,另外15个块在半满的时候停止了压缩。压缩和ASSM似乎没有特别好地结合在一起。当然,250个块仍然比没有压缩需要的714个块好很多,但这不是重点。

由于更新会导致标记扩展,并且在更新时不会再发生重新压缩,所以不得不考虑数据压缩的程度以及将要更新的数据量。一个典型的非压缩数据块在66到70行之间;但是当压缩(对于OLTP)时,块保持在156到301行之间,其中一半以上保持220到230行。

从悲观的角度来看,每个块有3倍的行数,这意味着您对压缩块进行更新的可能性是未压缩块的三倍。压缩的效果越好这些概率就越大。但是,当您考虑压缩方法和更新策略时,会出现真正的威胁。在我的表的第一个块中,我有十九个标记覆盖了11个连续的列,这意味着“真实”行中的一个字节表示11列数据 。 如果只是更新这些列中的一个,Oracle会将一个字节扩展为全11列!检查标记上的使用计数我可以看到在那个特定的块中有242行,其中182个引用了这些标记。这意味着“单列更新”可能引发11列的扩展,从而导致大量的行迁移。由于可能会遇到批量的更新数据或者插入数据,这就会发生大量的行迁移,从而出现大量的buffer busy waits。

压缩的另一个意想不到的结果是,当一行从一个块中移出时,它很可能会使得块的空闲空间只增加一点点(因为它是一个被压缩成用几个标记表示的行),所以与“正常”迁移不同,您不太可能发现一个行迁移来保护接下来的几个更新能够避免行迁移。

到目前为止,我只听到关于OLTP压缩的抱怨。这些抱怨中有一个共同点,就是大量的行迁移,额外的CPU和“buffer busy waits”。

当然,通过为pctfree设置一个合适的值,可以最大限度地减少行迁移。但是,Oracle使用了一些巧妙的技巧来最大化压缩,除非你非常了解数据和业务操作,否则很难决定一个好的值。

你需要祈求非常重复的数据是不需要更改的,并且那些被更新的列最好是惟一的,这样它们就不会以长列集合共享标记。但是你可能在尝试压缩和分析大量数据之后才能看到。不幸的是,我看到很多应用程序,每个表都有一个名字像last_updated_by的列,这个列很重复,但很可能随时间而改变。也很可能会进入多列标记,因此即使“真实”数据更改针对的是不期望被压缩的列,也会发生大量扩展。(当然,与基本压缩一样,如果列更新没有完成,则不会发生标记的扩展。)

| 总结

OLTP的压缩根据手册上说应该能够在更新过程中进行压缩,但是至少据我所知其实并不会。这意味着您可能很容易在更新时遭受大量的行迁移,这会导致额外的I/O,buffer busy waits,并增加CPU和闩锁活动。

如果您要使用OLTP压缩,则需要针对每个表找出合适的pctfree值,从而将行迁移保持在可接受的水平。

但是,由于OLTP压缩确实允许在普通插入时触发压缩,所以可以使用分区表来制定策略,使用OLTP压缩和较大的pctfree设置来“新建”分区,然后使用基本压缩重新构建较旧的分区。但是,如果要制定一个使用OLTP压缩的策略,一定要仔细考虑freelist管理和ASSM之间进行选择。如果将OLTP压缩与ASSM混合,可能会出现一些不良的副作用。

发表评论

电子邮件地址不会被公开。 必填项已用*标注