影响读取的因素
堆表的结构特征会对数据读取效率产生很大的影响。前面在介绍堆表结构和聚簇因子时,已经详细说明了其中的一部分影响因素。接下来,我们还会说明需要了解和注意的其他几个影响因素。
大范围数据读取的处理方案。
提高聚簇因子的处理方案及重构表时的注意事项。
大范围数据读取的处理方案(1)
在堆表中,由于数据是按照插入顺序被存储在磁盘的任意位置上,所以存储时所需要付出的代价相对较少。但是在读取满足特定查询条件(SQL语句中WHERE之后的条件)的数据时,所需要付出的代价则相对较大。当然,这并不是我们所能左右的事情。我们知道,当读取的数据量非常少时,不论怎么读取都能获得非常好的读取效率;但是当读取的是海量数据时,问题就从根本上发生变化了。
例如,当人体内的病菌数量非常少时,只需要通过打预防针,以增强免疫力,就能够治愈。但是,当人体内的病菌数量非常多时,以至到了威胁生命的程度,那就不再是仅仅通过注射预防的药物就能够治愈的问题了。再比如,假设读取1行数据需要花费0.01秒,那么读取10行数据也只不过是花费了0.1秒而已。但是如果需要读取1亿行数据,则所花费的时间为1 000 000秒,即277小时,将近12天。在查询数据时,等待0.1秒也许根本就觉察不到;但是如果要等待12天,则无论如何都是无法忍受的。
虽然这里举的这个例子有些极端但却是事实。在必须要处理海量数据的情况下,所采用的处理方案从根本上就应与面对少量数据时不同。因此,作为用户,我们首先应当认识到这一问题的严重性,绝不可以掉以轻心。很多用户认为,在处理海量数据时不应当使用堆表,事实并非如此,堆表在管理海量数据方面具有其他类型表无法比拟的优势。在堆表结构中,由于处理海量数据的各种解决方案(并行处理、分区、各种索引等)都可以使用,所以在现实中有很多用户都在使用堆表来存储海量数据。
即便在预测到将要处理的数据量会非常大的情况下,也仍然可以选择使用堆表来存储数据,这主要是因为这可以减轻数据插入时的负担。当然,如果插入数据时的负担并不是很大,则为了提高数据的读取效率,我们可以选择使用其他类型的表来存储数据。但是,在对数据存储速度要求非常高的情况下,相信没有哪个用户愿意为了给数据寻找一个好的存储位置而花费时间。
有些用户可能会把需要存储的数据暂时存储在临时位置上,等到闲暇时再将数据移动到磁盘的合适位置,但是这种方法并不像我们想象的那样容易。事实上,在大部分的RDBMS中,都是只在内存中对用户要求的数据进行处理,然后等到闲暇时再将处理结果从内存中移出并存储在磁盘的合适位置上。我们将此方式称为延迟写入(Deffered Write)。
在我们所开发的应用程序中,如果试图使用延迟写入的方法来处理数据,则由于每次都要通过编写代码来实现,使用起来会比较费劲。综上所述,在无法避免由于插入数据而造成的很大负担的情况下,不仅要对堆表予以充分的肯定,而且还要尽最大努力去寻找其他有效解决方案。在本书的后面会不断涉及此问题的不同解决方案,在此不进行过多说明。
在作者看来,堆表其实是最普遍的数据存储结构。乍一看,好像堆表只是在存储海量数据方面比较有效;但事实并非如此,也希望各位读者不要产生不必要的错觉。堆表所具有的唯一优点就是在数据插入时不需要采取任何特别的措施,只需顺其自然按照数据插入的顺序存储,因此减少了插入大量数据时的代价。仅此而已,再无其他任何优势可言。
依据表中所要存储数据量多少的不同,将表分为小型表、中型表、大型表。在这三种类型中,都会有一些对决定表结构产生一定影响的因素,在这里将予以详细说明。
小型表
首先来分析一下小型表的特征,所谓的小型表就是指表中所存储的数据量相对较少。这不仅意味着数据插入时的代价相对较小,而且还意味着数据被分散存储在较少的数据块上。假设把小型表中数据的存储区域视为一个个圆,则该圆的半径也会相对较小。例如小村子里的人们即使居住得比较分散,相对而言他们之间的距离其实也并不怎么远。然而,表的这些特征会对后来的数据读取有着非常大的影响。
在关系型数据库中,不论是何种类型的数据读取,每次最少都需要读取一个以上的数据块。由于小型表数据块数量相对较少,重复读取缓存在内存中的概率也相对较大,所以尽管是随机读也能获得非常好的数据读取效率。
如果小型表是在关键访问路径(Critical Access Path)上,则即使非常微小的差异也会由于频繁读取而被放大得非常明显。在此情况下,就有必要使用更加有效的方法(例如索引组织表,或者聚簇表)来存储数据。事实上,由于大部分小型表都不在关键访问路径上,所以除了特殊情况之外,一般没有必要采取额外的措施。
中型表
现在让我们再来思考一下中型表。对中型表进行具体定义是比较困难的,而且也没有必要必须对其下一个定义。这里所介绍的中型表是指,位于处理代价非常大的大型表与处理代价非常小的小型表之间的所有表。我们知道,在几乎所有的分类中,位于中间的类型通常都是最一般且所占比例最大的部分。
所谓的中型表不仅意味着数据插入时的负担不像大型表那样会对整个系统有着决定性的影响,而且还意味着在各种应用中频繁被读取的概率相对来说较大。让我们从常识的角度来思考一下中型表,反正数据插入时都需要付出一定的代价,还不如采用有利于数据读取的固定存储方式来存储数据。这样尽管在某种程度上增加了一些数据插入时的代价,但换回的却是高效率的数据读取。
虽然这种想法具有一定道理,但由于按照固定存储方式只能确保一种存储顺序,所以也只是在读取特定列时能够获得较好的效率。对于其他列而言,根本无法改变其值处于分散状态的事实。这个概念在介绍聚簇因子时,已经进行了详细说明。
大范围数据读取的处理方案(2)
换言之,使用以某个特定顺序存储数据的方法并不能满足所有的读取要求。这主要是因为这种存储方法只能确保在特定的读取类型中获得较好的效率。通常情况下,对大部分表的读取要求并不仅局限于某几个特定的读取类型,而是多种多样的。所以,从理论上来看,根本就不存在一种能够满足所有读取要求的数据存储方式。
从任意列的角度来看,不论采用何种方式对数据进行存储,整个表中的数据都将被认为是分散地存储着的。然而,即使无法通过使用存储方式来提高读取效率,我们也不能放弃寻找其他能够提高随机读取效率的解决方案。
如果某个特定的读取类型不仅具有非常重要的地位,而且值得为确保其读取效率而采用一些必要措施,则我们应当对其予以高度重视。这就好像侍卫为了确保皇帝的安危而提前做好各种防护措施;又如为了提高首尔至釜山快速列车的速度,直接修建了一条名为京釜线的高速铁路。
至此我们的结论就比较清晰了:首先,选定最为重要的读取类型;其次,通过调查分析来决定是否有必要为该读取类型采取一些特殊的措施。在此情况下,我们还应当集中精力寻找除了此方法之外的其他解决方案,如果能够找到自然是最理想的;如果费了九牛二虎之力也没能找到,则只能采用这种方法了。对于世界上的所有问题而言,我们始终追求的最理想解决方案就是以最少的代价换取最大的回报。如果各位读者对经济学比较了解的话,则会知道在经济学中所追求的是以最低的成本支出换取最大的利润回报。所以我们在解决问题时,也应当追求一下"经济效益"。
如果决定按照某个(或多个)特定列的顺序来存储数据,则必须为了提高其他列的读取效率而努力寻找解决方案。由于这里所涉及的解决方案几乎是本书中所要讨论的主要话题,所以在此就不对其进行详细说明了。
需要再次强调的是,尽管为了提高聚簇因子而选择使用了特定的存储结构,但时刻都不能忘记表的结构依然是堆表。这类似于我们为了提高速度而修建了高速公路,但是一般的国道依然不会被拆除。
就像如果由于修建高速公路的支出太大,大到会对财政支出带来一定的负担,那就需要我们反复探讨一样,如果数据插入时的代价超出了承受的范围,则就需要我们对使用特定位置存储数据的方式予以重新考虑。这里所介绍的原理不仅适用于中型表,也适用于大型表和小型表。但不同的是,对于大型表而言,不仅数据插入的负担比较大,数据读取的类型也比较多,所以采用堆表会比较有效。
迄今为止,我们已经从不同的角度对堆表进行了全面分析,从中可以发现堆表就是最一般的数据存储结构。在大部分情况下,由于数据被分散存储的顺序与我们所要查询的数据顺序之间并没有任何必然的联系,所以只能通过大量的数据读取来查找我们所期望的数据,从而使系统需要付出大量额外的代价。综上所述,我们无法单一地只通过选择数据的存储结构来提高所有数据读取类型的效率。
大型表
最后让我们再来考虑一下大型表。对大型表的分类方法有很多,在这里我们将其分为以下三类。
第一类:单纯的存储型表。在这里以管理日志信息的表为例来进行说明,这样的表既不会被经常使用,也不会有多样化的读取要求。只有在特殊的情况下,才有可能要求按照特定的读取类型读取数据,或对大量的数据进行扫描,并且日志表还要求具有较快的存储速度。综合这些特征和要求,使用堆表来存储此类数据是最佳的选择。另外,由于数据增长的速度会比较快,所以最好能够为其创建分区。
第二类:像顾客表这样虽然存储着大量的数据,但主要是以随机读取为主,并不存在多样化的读取类型的表。这种情况下,堆表仍然是比较适合的选择。一次性向这样的表中插入大量数据的情况非常少见,范围处理(要求处理的数据范围相对比较大)的情况也不会经常出现。
尽管按照某个列对表进行了分区,但是经常会出现并不是只读取某个特定分区的情况,而且为了某个特定部分而对其进行单独操作的机会也并不多,所以即使创建了分区表或聚簇表,也不会获得太大的效益。
第三类:像销售表这样的表不仅数据急速大量增加,而且具有多样化的数据读取类型。一般情况下,拥有这种特征的表对系统会产生极大的影响。不论从数据管理的角度还是从数据读取的角度来看,都具有非常大的负担。如果我们没有为这种类型的表制定出合理的解决方案,那么各种问题就会接踵而至。
如果急速增加的数据对管理造成了很大的负担,则应当当机立断为其创建分区。关于如何更好地使用分区的相关内容将在后面予以详细说明。由于经常需要对这种类型的表执行大范围数据扫描,所以如果再扫描了大量本不应该扫描的数据,则会导致非常严重的后果。
我们为如何只对所需要的数据进行读取的问题提供了多种有效的解决方案。其中构建有效的索引和确保最优化的SQL执行计划是其中最为重要的两个方法。除了这些解决方案之外,我们优先应该解决的问题就是,如何决定只能按照一种顺序存储数据的表结构。
不论是调整索引结构、修改SQL语句,还是改变执行计划,相对而言都比较容易,但改变表的结构却不是一件容易的事情。
原文链接:http://book.51cto.com/art/201010/231701.htm