3月22日 理解表的存储结构 (3)那些逝去的老参数
有几个参数可能随着Oracle数据库版本的变化逐渐的逝去了,不过那些参数可能承载了很多老DBA的荣誉和苦泪。它们是pctused、freelists、freelist groups。随着自动段空间管理(ASSM)在9i的推出,现在新的数据库很少还在使用手工段空间管理(MSSM)。自动段空间管理和手工段空间管理最大的区别���于空闲块的管理上,自动段空间管理出现之前,Oracle的空闲块是通过FREELIST机制来管理的,表和索引在被创建的时候必须指定PCTFREE、PCTUSED、FREELISTS、FREELIST GROUPS 等参数。一个被格式化后的数据块被挂在FREELIST上,这样需要插入数据的前台进程可以在FREELIST上找到可以插入数据库的块,插入相关的数据,数据插入后,如果数据块中的空闲空间小于PCTFREE指定的比例,那么这个数据块就不能再次被插入数据了,这个数据块将从FREELIST上摘除,下次插入数据的时候,就会插入到其他的数据块中。这个数据块中如果有数据被删除后,空闲空间低于PCTUSED的指标,那么这个数据块将会再次被放到FREELIST上,这样这个数据块就可以再次插入数据了。有人可能会有疑问,一个比较“满”的数据块,如果删除了数据,那么不就马上可以被插入数据了吗?这个时候马上就放回FREELIST上不就行了,还用PCTUSED干什么呢?实际上Oracle的这个设计是十分优秀的,如果不设计PCTUSED��个参数,我们会碰到这样的情况,一个数据块刚刚插入数据后被从FREELIST中摘除,马上又因为删除数据被再次放入FREELIST,那么就可能会产生抖动,FREELIST的性能就会因为频繁重组而急剧下降。
同样的道理,在一个MSSM管理的段中,如果PCTFREE和PCTUSED两个参数设置的不合理,那么这种抖动现象还将会出现。比如说我们设置PCTFREE为30,设置PCTUSED为65,如果行长度较大的话,如果一个数据块中刚刚插入一条记录后马上又删除了2、3条记录,这种情况下,这个数据块可能出现刚刚被从FREELIST上摘除,就马上被再次放入FREELIST的现象。从对MSSM管理空块的方法,我们可以看到设置合理的PCTUSED参数的重要性,如果PCTUSED参数设置过高,可能导致FREELIST的抖动,如果PCTUSED参数设置过低,可能导致数据块中存储的记录数过少,影响全表扫描操作的性能。
讨论完PCTUSED我们再来看看另外一个重要的段空间管理相关的参数FREELISTS,FREELISTS是用于存放可插入数据的数据块的,FREELISTS存在于perment data segment、temporary data segment、index segment、rollback segment中,一个段中有几种类型的FREELISTS:
l MASTER FREELISTS
l PROCESS FREELISTS
l TRANSACTION FREELISTS
一个段中只有一个MASTER FREELISTS,这个FREELISTS在段创建的时候就创建了,段中的高水位以下的空闲块都挂在MASTER FREELISTS上。当某个前台进程需要插入数据的时候,就可以到MASTER FREELISTS上去查找空闲块,查找操作从FREELISTS的头部开始,直到好到可以插入数据的块为止。
明眼人看到这里马上就会看出问题来了,FREELISTS是一个串行的机构,如果有大量并发的会话需要插入数据的话,FREELISTS就会成为一个瓶颈。实际上ORACLE对此也早有对策,Oracle存在另外一种Freelists:Process Freelists。Process Freelists和Master Freelists是一种主从协同机制,每个Process Freelists上链接了一组空闲块,这些空闲块是从Master Freelists上摘取的,前台进程要插入数据的时候,首先必须锁定一个Process Freelists,然后从这个Process Freelists上查找一个空闲的数据块用于插入数据。当Process FreeLists上也没有可用的空闲块的时候,会锁定Master Freelists,从中分配一组空闲块到Process Freelists上,然后再从Process Freelists上分配空闲的空间。听起来似乎很不错,Oracle的Process Freelists的设计避免了Freelists成为并行插入操作的瓶颈。不过大家要注意的是,Oracle缺省的Freelists参数值是1,在Freelists参数为1的时候,段头中只有一个Master Freelists,而不存在我们希望的Process Freelists。
不幸的是,绝大多数用户在使用MSSM的时候并没有设置Freelists参数,他们并没有留意到这种设置带来的负面影响。这种负面影响到底有多大呢?老白遇到过的一个最为极端的案例是一个移动公司的短信平台,这个平台每天需要插入的数据量在几千万条记录到几亿条记录,由于Oracle数据库使用的是8i,因此只能使用MSSM,在这个平台上我们进行了一个测试,在8个并发插入进程的情况下,如果FREELISTS设置为8,性能比FREELISTS设置为1 提高了5.7%。在一半的情况下,可能性能提升没有这么明显,不过如果Freelists参数设置不合理,造成1-5%的性能影响还是很可能的。
Freelists的机制设计的确实比较巧妙,似乎我们的所有问题都已经被解决了,不过细心的读者还会有一些疑问,那些已经标志为“满”的块怎么再次回到Freelists上呢?难道只要数据被删除了,块使用率低于PCTUSED,这个数据块就马上被放到Freelists上吗?这么做似乎也不太稳妥,一旦这个删除操作回滚了,那么岂不是又要再次把这个块从Freelists上摘除吗?实际上Oracle早就为这种操作设计了算法,这就是Transaction Freelists。一个段缺省有16个Transaction Freelists,如果某个事物要修改这个段,首先会搜索Transaction Freelists,如果找到了这个会话的Transaction Freelists,那么就继续使用,否则这个会话就会继续找一个空闲的槽位,如果找不到空闲的槽位,这个会话就会试图去扩展一个新的Transaction Freelists,如果要扩展的时候,正好Segment Header中已经没有空闲空间了,那么这个会话就只能等待其他会话提交后腾出空闲的槽位。这种等待有点类似于我们前面说到的事务槽的等待,不过事务槽的等待产生于数据块中,而Transaction Freelists的等待是存在于Segment Header中。如果我们使用MSSM,并且Segment Header的等待较为严重的话,我们就应该检查检查是否是由于这个原因引起的等待。
当某个会话申请到Transaction Freelists后在某个数据块中删除了一些数据,这个数据块的使用率低于pctused参数后,这个数据块就被挂到Transaction Freelists上了,这个时候这个数据块可以被插入数据了,不过由于这个数据块只是在本会话的Transaction Freelists上,因此在这个事物提交之前,只有本事务可以在这个数据块中插入数据,只有等这个事物提交后,Transaction Freelists上的数据块才会被挂到Master Freelists的尾部,这个时候其他的会话也可以使用这些空闲块了。
是不是很巧妙的算法?Oracle通过这三种Freelists,十分好的解决了插入数据时的性能问题,将Freelists机制的瓶颈降到了最低。
对于MSSM来说,还有一个十分重要的参数Freelists Groups,这个参数一般来说被认为是OPS/RAC相关的参数,实际上这种认识也存在一定的片面性。不过我们还是先从这个参数在RAC环境下的应用说起。对于RAC系统来说,在MSSM环境下,Freelists的算法和单实例环境是十分类似的,了解CACHE FUSION机制的读者这个时候就会担心了,这种机制可能在RAC环境下产生严重的GLOBAL CACHE相关的性能问题。比如不同的实例上都会对某张表进行插入,那么如果INSTANCE 1上有一个会话在BLOCK 10中插入了一条记录,然后INSTANCE 2上有一个会话在BLOCK 10中插入了另外一条记录,INSTANCE 1再次在BLOCK 10中插入记录,这样的操作如果很多的话,那么就会成为一场灾难。这个数据块在两个节点中不停地来回传播,形��了GLOBAL BUFFER BUSY,这种数据块一旦躲起来,那么这个RAC系统的性能就会急剧恶化。
事实上,Oracle有足够的技术来避免这种悲剧的发生,Freelist Groups就是为解决这个问题而设计的。比如我们目前的RAC环境是一个2个实例的数据库,我们对某张表设置了Freelists Groups参数为2,那么这张表创建完毕后,在这张表的Segment Header中会有两个BLOCK,专门用于存储两组不同的Freelists(我们称之为Freelists Block),这种情况下,Segment Header Block里只有一个Super Master Freelists,上面挂载了这个Segment中的空闲块,而在每个Freelists Block中都有一个Master Freelists和若干个Process Freelists(由参数Freelists参数确定),在这两组Freelists上分别挂了一组空闲块,当某个实例上的会话需要插入数据的时候,会通过INSTANCE_ID、PID、INSTANCE数量、Freelists Groups参数等因素来选择一组Freelists,从这组Freelists上选取空闲块。只要我们设置的Freelists Groups等于INSTANCE的数量,我们就可以确保每个实例都有自己独立的Freelists组,由于一个数据块只能挂载在一个Freelists上,这样就确保了不同实例插入数据时,不会选择不同的数据块(Freelists Groups大于Instance 数量一般也可以确保在大多数情况下不会出现不同实例共享相同的Freelist Groups,由于其选择算法较为复杂,我们这里不做详细的讨论)。
这种机制就很好的避免了我们刚刚所担心的问题,这是不是很美呢?而事实上不幸的是,我在十多年的优化工作中,还没有碰到一套系统为RAC环境设置了Freelist Groups参数,所有的系统都是用了缺省值,而Oracle的缺省值恰恰是每个段只有一个Freelists组。更为不幸的是,一旦段创建之后,Freelists Groups就是固定的,我们无法动态的扩展Freelist Groups,如果要扩展Freelists Groups,我们必须对��个段进行重建。因此在做数据字典设计的时候,开发人员应该事先为RAC环境设计好Freelist Groups参数,以避免今后扩展带来的麻烦。在考虑Freelist Groups参数的时候,我们不仅仅要考虑到RAC环境,而且要为今后RAC节点的扩展预留足够的Freelist Groups。
看到这里,读者应该理解了Freelist Groups在RAC应用中的作用,而事实上,Freelist Groups不仅仅在RAC环境下有用,在一些极端条件下,这个参数在单实例环境中也能发挥作用。在一个插入并发量很大的环境中(比如有几十甚至上百个并发会话会对同一张表进行插入),我们可能经常会观察到这张表的segment header的等待,这种情况下,设置Freelist Groups大于1可以减少由于Freelist争用而导致的Segment Header的热块争用情况,多个Freelist Groups可以将多个对Freelists的并发操作分散在多个Freelists Block中,从而提高并发插入的性能。
我们在这一节中讨论了大量的Freelists和Freelist Groups参数在解决大并发量插入时的性能问题上的优点,这并不说明我们可以随便滥用这两个参数。这两个参数除了优点之外,也有一些缺点。多个Freelists可能导致数据块中的数据填充率变低,段的高水位推进过块,存储相同数量的记录,可能会占用更多的数据块。对于表扫描较为严重的应用来说,我们需要慎重考虑二者的正面和负面的影响,从而权衡使用这组参数。一般来说,在OLTP系统中,插入并发量很大的表,一般来说表中的数据量很大,应用访问这些表的数据的时候,大多数情况采用索引扫描,因此加大这组参数的副作用较小。而对于OLAP系统,我们切不可为了提高数据并发加载的性能而轻易加大这组参数,因为OLAP系统的数据经常需要进行全表扫描,这组参数的使用可能会影响全表扫描的性能。
谈了这么半天,可能有读者会说了,现在我们主流的数据库都已经是10g甚至11G了,从oracle 9i开始就支持自动段空间管理了,讨论手工段空间管理是不是已经没有多大意义了呢?事实上对于DBA来说了解一些MSSM的技术还是有意义的,一方面是我们很可能碰到8i的数据库,也可能碰到一些9i或者10g的数据库,它们是从8i升级上来的,可能很多表空间都是MSSM管理模式的。甚至我们可能为了规避某些BUG(比如说9i的ASSM表空间由于object reuse导致的ORA-600 [kcbget_xxx],ORA-600 [kcbnew_xxx]之类的BUG)而是用MSSM的表空间。甚至在某些极端的情况下,为了避开ASSM中BITMAP带来的性能问题而反过头来是用MSSM(在绝大多数情况下ASSM的BITMAP机制在性能上会优于Freelists管理模式,不过在某些特殊应用情况下,可能会反过来)。
通过上面的讨论,大家是否理解了MSSM下段参数的设置要点了呢?实际上,在具体的应用环境中,可能情况远比今天老白和大家一起探讨的要复杂的多,如何在纷繁的头绪中处理好这几个看似很简单,但是又很让人头痛的参数呢?这需要数据字典的设计者首先要对MSSM管理的机制十分了解,另外要对自己应用的特点十分了解,才能做出合理的选择。最后要提醒的是,当你无法做出合理的判断的时候,做个试验可能是比较明智的选择,到底哪种选择更好,应用系统说了算。