《海量数据库解决方案》之聚簇表的代价

1.3.4  聚簇表的代价
单表聚簇解决了关系型数据库中最难解决的问题——大范围数据处理。复合聚簇则在很大程度上提高了特定表连接的效率。因此,聚簇的这些特征在提高数据读取效率方面发挥着巨大的作用。然而,我们知道“世界上没有免费的午餐”,有所得必然会有所失,虽然我们使用聚簇实现了提高数据读取效率的目的,但我们也必须为此付出相应的代价。
例如某个棒球队由于缺乏优秀选手而每次都无法赢得比赛,对这个棒球队而言,当务之急就是物色一些优秀的选手来弥补缺陷,但需要考虑的就是是否能够支付得起优秀选手的高额工资。如果能够利用一名非常优秀的选手来替代多名能力相对较差的选手,则相对整个球队的费用开支而言,其实并没有太大的增加。
因此,问题的焦点就可以归纳为两点:第一,能在多大程度上解决紧要问题;第二,为了解决该紧要问题需要付出多大代价。
赋予聚簇什么任务?
如果有一定财力来雇佣优秀选手,或者除了雇佣优秀选手再无其他选择余地的情况下,自然也就只能这样做了。那么究竟雇佣哪个位置上的优秀选手呢?这需要进行全面的权衡。当然,最理想的方案是为每个位置都物色一名优秀选手。但如果受各种条件的限制而只能雇佣一名优秀选手的话,则我们肯定会选择能够为棒球队带来最大收益的选手。
同理,在我们能够承受由于使用聚簇而所需付出的代价,或使用聚簇成为唯一选择的情况下,也就只能选择使用聚簇了。在使用聚簇时,它所能发挥的作用大小会随着我们为之所赋予的任务的不同而不同。就像“杀鸡焉用宰牛刀”这句格言所隐含的意义一样,如果只通过对现有队员的合理训练就能够赢得比赛胜利,则完全没有必要以高额的代价去雇佣明星球员。
通常情况下,为了能够做出正确的决策,首先应当对我们目前所拥有的资源进行全面分析,并列举出所有可能的解决方案,然后从中选择一个代价最小、收益最大的方案。如果棒球队不论怎么合理训练现有球员也无法取胜,或者认为雇佣一名优秀球员将能给球队带来取胜的希望,则完全有必要对雇佣新球员的方案予以全面深入的分析。
同理,在选择使用聚簇时也需要做出全面的分析,不能只单独地考虑是否使用聚簇。我们不仅要对利用聚簇来解决的读取类型进行收集分析,还要寻求是否存在能够解决问题且所需代价比较小的索引,并且还要分析聚簇和索引是否会由于角色重复而导致资源浪费。我们要从发展和联系的角度出发,合理地为它们赋予独立而明确的任务,以避免资源浪费。总之,我们在使用聚簇时需要充分地考虑它的代价。
关于这些内容将在本部分的第4章构建索引的战略方案中予以详细说明。
使用聚簇所需要付出代价的程度? 
聚簇只是在数据查询时能够提高数据的读取效率,但在数据的插入、修改、删除中却需要付出额外的代价。因此,如何能把效率和代价较好地协调在一起是使用好聚簇的关键问题。在我们说明使用聚簇的准则之前,首先应当对聚簇的代价有一个正确理解,因为它对正确地选择聚簇有很大的帮助。
对于使用聚簇所需要付出的代价无法使用具体数值来衡量,所以在这里以各个操作为单位对其内部所需要付出的追加代价予以一一说明,以供各位读者对是否选用聚簇做个参考。在这里我们以插入、修改、删除操作为对象,对其内部的执行步骤以及所需代价予以详细说明。
第一,插入代价
当我们向聚簇表中插入数据时,由于数据行必须要求被存储在指定位置上,所以所需要付出的代价自然也就比把数据行插入到一般表中时所需要付出的代价大。在MSSM里,当我们向一般表中插入数据行时,只需要从Free List中获得为其所分配的空间就可以无条件地进行存储。如果用来存储数据行的数据块空间达到了PCTFREE,则会再次要求Free List为其分配新的数据块。
由于每个行的存储位置会随着其所具有的聚簇键值的不同而不同,从而就增加了向Free List申请分配数据块的次数。例如,向一般表中插入数据行时,只需要向Free List提出一次申请就可以在为其所分配的数据块中存储100行数据,但聚簇表在最坏的情况下为了存储100行数据就有可能向Free List提出100次为其分配数据块的请求。
在事务处理中尽管同时向聚簇表中插入几十行、几百行数据时不会对系统的执行速度有任何影响,但如果同时要求插入数千行乃至数万行数据时则对执行速度的影响就应该值得我们注意了。在此情况下,我们应该通过对事务处理的频度、执行时间段、对整个系统的影响等问题的仔细斟酌来决定是否选用聚簇表。
我们在前面所介绍的数据行需要被存储在指定的位置上,这里的“指定的位置”是指如果创建的聚簇键已经存在,则数据行必须被插入到与其聚簇键值所对应的单位聚簇中。虽然拥有新聚簇键值的行与已经存在于磁盘上的对应单位聚簇之间在位置上可能具有一定的差异,但DBMS允许这些具有新聚簇键值的数据行被临时存储在任意位置上,这主要是因为在聚簇索引中存储着单位聚簇的位置信息,不会影响到对这些新插入数据行的读取。
为了能够提前预知向聚簇表中插入数据行的代价,所能采取的最好方法就是创建一个测试用的聚簇表,然后向其中插入一定量的数据。可以使用“INSERT INTO…SELECT…WHERE ROWNUM<=100”SQL语句来向聚簇表中快速插入一定量的数据,然后再观察其执行速度。
事实上,在插入数据时只要能够遵循实际数据插入的顺序就能够获得正确的代价。为了能够遵循数据的实际插入顺序,应当按照生成数据的实际日期来从其他表中读取并插入到聚簇表中。换言之,也就是在查询语句的WHERE条件中加上日期条件。当然,有时为了能够清晰地观察到插入时的代价,有必要创建一个能够确保按照部分范围扫描的方式执行SQL语句的日期索引。 
事实上,向一般的表中插入数据时也存在着与此相类似的问题。在最开始时,由于没有为表创建索引,所以可以无条件地把数据存储在为其所分配的数据块上。但在实际环境中,由于基本上都已经创建了索引,所以必须在具有多个索引的状态下向表中插入数据,这就是数据插入时聚簇表和一般表代价相似的主要原因。
向一般的索引中插入新的索引行时也需要寻找到与其相对应的位置才能够进行存储,从这个角度来看,同样存在着与聚簇表相似的导致插入代价增加的原因。尽管这个代价比聚簇的代价小,但当一般表具有多个索引存在时,这一代价是绝对不可轻视的。
在对表执行了重构操作后,所有索引原封不动而追加使用聚簇,则必须付出额外的代价。像前面所强调过的那样,如果我们对现有的索引分配了合理的任务,并在现有条件下使其作用得到最大程度的发挥,则可以在很大程度上缩减索引的数量。通常情况下,我们为了提高大范围数据处理的执行速度,会不断地创建索引,这也是索引数量不断增加的主要原因。聚簇也是在处理大范围数据方面具有非常好的效果,所以聚簇的追加使用自然也就减少了索引的数量。
尽管使用聚簇的代价比使用索引的代价大,但是从使用聚簇可以减少索引数量上来看,总的代价却比我们想象的要小。这就像前面所列举的那个例子一样,尽管棒球队雇佣一名球星需要支付高额的工资,但同时却可以解雇几名水平一般的球员,所以从总体上来看并没有给球队带来太大的经济负担。
第二,修改代价
对聚簇表列值的修改可以从两个方面来进行考虑:第一,聚簇键列值的修改;第二,一般列值的修改。
由于聚簇表中的行是根据聚簇键值来进行存储的,对聚簇键值的修改不仅意味着列值的变化,而且也意味着行必须被移动到其他单位聚簇中去。当聚簇键值被修改时,为了确保在行被移动到其他单位聚簇的过程中ROWID的值不发生变化,就必须按照行迁移的原理来执行,关于行迁移的内容在前面已经进行过详细说明。
由于聚簇键值的变化而导致聚簇内部发生追加性的插入操作,即修改之后的行需要被移动到其他单位聚簇中的操作,在某种程度上可以被认为是插入操作。这与在一般表中当某个行不能被存储在已有数据块中时所发生的行迁移现象相似。唯一不同的是在一般表中行将要被迁移到由Free List为其所分配的任意位置上,而在聚簇表中行将被迁移到指定的位置上。
事实上,并不会因为聚簇键列值的修改而需要付出什么特别的代价。对聚簇键值的修改所造成的真正影响在于由于聚簇链接现象的发生而使得聚簇因子变坏,即具有相同聚簇键值的行被分散地存储在多个数据块上,从而在读取数据时增加了所必须读取的数据块数量,这就使得原来创建聚簇的目的在很大程度上被破坏了。
由此可见,在允许的情况下尽量不要把修改频繁的列指定为聚簇列来使用。在实际应用中,由于对聚簇键值的修改而导致代价增加的现象几乎不存在,所以在决定是否选择使用聚簇时并不需要对修改操作时的代价予以过多的考虑。
由于对非聚簇键列值所执行的修改操作与一般表相同,所以,如果没有把修改频繁的列指定为聚簇键列,则完全没有必要对聚簇修改时的代价予以过多的考虑。
第三,删除代价
现在再来观察一下聚簇表中行被删除时的代价。先从结论来说,即使对聚簇表中的某个行执行了删除操作,也不需要再次执行任何追加性的处理。就像在前面所说明过的那样,可以把单位聚簇看做是一般表的行,单位聚簇中的行可以看做是一般表的列。因此,单位聚簇中的某个行被删除也就与一般表中的某个列值被删除相类似。由于聚簇索引中的一个索引行与一个单位聚簇相对应,所以即使聚簇表中的某个行被删除了,聚簇索引中的索引行也仍然存在,不会被删除。
需要注意的是,即使聚簇表中的全部行都被删除,聚簇索引仍然存在,不会被删除。严格地讲,在聚簇表中执行删除操作时的代价反倒比在具有索引的一般表中执行删除操作时的代价小。但需要强调的是,表的删除与行的删除是完全不同的问题。
对一般表执行删除操作(DROP)时,并不是先找到该表的位置然后再进行删除,而是直接从数据字典中删除其相关信息,并收回为其所分配的空间。由于不需要执行查询操作,所以执行速度非常快。由于DROP命令是DDL语言,所以在执行DROP删除时,并不会把删除信息存储在Rollback Segment中,自然也就无法对其执行回滚操作。
删除聚簇表在聚簇内部实际上执行的是“DELETE”操作。理由在前面已经说过了,聚簇表中的单位聚簇可以被看做是一般表的行,单位聚簇中的行可以被看做是一般表的列,删除表命令“DROP”是将表中的所有行全部删除而不是只删除单位聚簇中的行,所以从聚簇的角度来看,其执行的是“DELETE”操作。
如果从复合表聚簇的立场来思考这样做的原因,就会觉得它是非常合理的。假设在复合表聚簇中有两个表被存储在一起,如果其中的某个表被删除了,从单位聚簇的角度来看,也只不过是自己的某些行被删除了而已。
在从复合表聚簇中删除某个表时尽管使用的是DDL语言,但仍然需要从回滚段获得空间并存储被删除的数据。因此,执行DROP操作的代价就如同对表中的所有行执行了DELETE操作所需付出的代价一样大,这种代价不仅体现在删除操作上,而且还体现在对回滚段空间的需求上。当回滚段的空间不足时,由于会显示回滚段空间不足的提示信息并强制性终止操作,所以建议最好不要使用这种方法,转而寻求其他更有效的方法。
我们使用聚簇的主要目的就是为了确保拥有较好的聚簇因子。删除表中的全部数据也就是通过执行命令“DROP”来达到目的的;但也可以使用另外一种方法,即在新的聚簇中创建一个表,然后把将要删除的表中的数据插入到新创建的表中,最后将新创建的表名更改为原来的表名。该方法既能减少删除时的代价,也能减少对回滚段空间的需要,可以认为是一个比较理想的方法。
事实上,在删除无用数据时,删除聚簇所需的代价要比删除表小。使用“DROP”命令或者“TRUNCATE”命令可以快速地删除数据。下面是这两个命令的使用范例。
DROP CLUSTER cluster_name INCLUDING TABLES CASCADE CONSTRAINTS;
TRUNCATE CLUSTER cluster_name REUSE STORAGE;
使用命令DROP/TRUNCATE删除聚簇的方法与删除一般表的方法相同。使用“DROP”删除聚簇时,不仅聚簇表中的全部行被删除,而且连同数据字典里的数据也一起被删除。如果将要删除的聚簇中仍然有表存在,则使用子句“INCLUDING TABLES”连同表一起删除。如果聚簇中表之间存在着约束条件,则应当使用“CASCADE CONSTRAINTS”命令连同约束条件一起删除。
命令“TRUNCATE”与命令“DELETE”相似,只是删除表中的行,它的执行速度与命令“DROP”基本相同。因此,如果在重构聚簇表时想重新使用当前聚簇表所占据的空间或维持数据字典中当前聚簇表的信息,则使用“TRUNCATE”命令比较有利。维持当前空间能够被再次使用的具体方法如下所示。
TRUNCATE CLUSTER cluster_name REUSE STORAGE;
如果不指定“REUSE STORAGE”子句,则由于默认是“DROP STORAGE”,所以当前表的存储空间将被一同删除。
截至目前,通过对聚簇表的插入、修改、删除操作代价的介绍,相信各位读者应该对聚簇的代价已经有了一个全面的认识。使用聚簇时对我们而言最为重要的是,如何只通过对将要聚簇化的各个表现实特征的合理结合,就可以实现提高数据读取效率的目的。关于这个问题将在本部分的第4章构建索引战略方案中予以详细说明。

原文地址:https://www.cnblogs.com/broadview/p/1888813.html