关系型数据库中的压缩技术
计算机存储的容量限制仍然日益成为IT系统的瓶颈。其主要原因有两个:第一,信息革命导致人们产生了比过去多得多的数据。巨大的数据库系统每时每刻都在产生海量的新数据。第二,随着计算机存储能力的增长,人们倾向于永久性保存所有的数据。例如,在信息革命早期,证券交易系统往往只保存近一段时间的交易细节数据。如今,人们倾向于保存所有能够被保存的数据:每一次交易,每一通电话,网站的每一次点击,交换机中的每一回通信等。
在这种趋势下,计算机存储承担着越来越沉重的压力。尤其是在企业级应用中,为了保存海量数据而在存储上投入的成本,往往已经到了令人吃惊的地步。
在数据库中使用压缩技术,是为了解决(或者至少缓解)这种压力所做出的努力之一。这种技术的定义十分简单:对存储在数据库中的数据进行压缩,从而减少占用的磁盘空间,同时又尽量不影响数据库的其他操作。
很容易想象这一技术产生的后果。被压缩后的数据能够显著地减少占用的磁盘空间,从而降低整个系统的存储成本。然而对数据进行压缩和解压缩,需要更多的CPU时间。在对速度要求十分苛刻的数据库系统中,这种CPU时间的额外支出,是否会导致效率的严重降低呢?
让我们全面地审视压缩技术引起的得失。在CPU时间上,会有额外的支出。但是,由于压缩后的数据占用的磁盘空间减少了,这意味着系统用于I/O的时间也会相应的减少。众所周知,数据库系统最大的瓶颈在于I/O,I/O速度的增长却远远跟不上CPU按照摩尔定律的增长。因此,从CPU时间上支出的成本,可以在I/O速度的提高上补偿回来,而且还可能有盈余。压缩后的数据库,不但会占用更少的磁盘空间,甚至还可能有更快的速度。
然而在实际项目中,还要考虑到多种因素的权衡,情况可能会非常复杂。幸运的是,主流的几种关系数据库在实践中都已经正式支持压缩技术。目前,数据库压缩技术主要仍然被应用于数据密集型的OLAP,而不是运算密集型的OLTP,但这并不意味着它不能被应用于OLTP。
压缩方式
目前,几乎所有的关系型数据库中应用的压缩方式,都是基于字典的压缩方式。基本原理是,将数据中重复出现的信息抽取出来,并用比较简短的符号予以代替,从而达到压缩的效果。举例来说,如果数据中重复出现了“Personal Computer”这个字符串,那么它就会被识别为一个模式(Pattern),然后所有这个字符串出现的地方都会被一个对应的符号(Symbol)代替,比如数字1。所有的模式和对应的符号都会被存储在字典里面(Dictionary),字典被用于压缩和解压缩(也就是对Pattern和Symbol进行相互替换)。当然,真实的应用比这要复杂得多。但是,理解了字典压缩的原理以后,我们已经可以从不同的角度对不同的压缩技术进行区分。
- 按建立字典的方式区分:手工建立字典和自动建立字典。手工建立字典,意味着数据库不能自动搜索数据中的重复数据,必须人工输入所有的模式才能建立字典。这种方式出现在数据库压缩技术的早期,目前已经基本被淘汰。自动建立字典则意味着数据库会自动搜索模式而无需人工干预。
- 按字典应用的范围区分:表级别的字典和块级别的字典。表级别的字典意味着在整个表的范围内搜索模式并建立一个唯一的字典,而块级别的字典则在每一个块上建立单独的字典。其中,块是关系型数据库中的一个术语,是存储的最小单位。
- 按存储的方式区分:列压缩和行压缩。这涉及列存储和行存储的概念。行存储表示数据库中包含不同字段的同一行被连续存放。列存储则表示包含不同行的同一字段数据被连续存放。同一字段的数据出现重复的可能性较大,这意味着基于列的压缩可能有更高的效率,但这和传统关系型数据库的存储方式相悖。由于二者互有利弊,数据库厂商往往通过一些技巧来避免其缺陷,使之适应实际使用,甚至混合使用这两种压缩方式。
压缩相关的操作
虽然关系型数据库使用的压缩算法本身不太复杂,但是由于压缩技术改变了数据存储的底层结构,因此涉及数据库操作的方方面面。下面是一些主要的相关操作:
- 数据查询。当接收到查询请求时,数据库系统从磁盘中读取已被压缩的数据,必须先经过一个解压的过程,将数据还原为未压缩的形式,再返回给查询请求。
- 数据更新。当进行Insert和Update操作时,数据需要经过压缩之后才被存储。理论上来说,Delete操作只需要简单地删除数据,而无需进行压缩或解压缩。但是事实上,在某些自适应的压缩技术中,对已有数据的更新到达某一阈值时,会导致字典的自动更新(因为字典已经不能再适应当前的数据)。这意味着,IUD操作都有可能导致字典的重新创建(或删除)。
- 数据装载。这和插入数据的过程类似,数据将会先被压缩然后被存储。在某些情况下(例如,当DB2的Automatic Dictionary Creation技术被启用时),装载数据时还可能同时创建字典。
- 表整理。在整理表时,根据当前表被标识为压缩或未压缩,将会对数据进行相应的压缩或者解压缩处理。表整理是对整个表进行充分压缩的有效手段。
- 压缩率评估。数据库一般会提供一个操作,在未被压缩(或未被完全压缩)的数据表上进行评估,预测能达到多高的压缩率。
- 索引(Index)压缩。索引压缩的算法与关系型数据压缩不太一样,本文不进行深入讨论。
- 大对象(LOB)压缩。大对象不使用关系型数据的行存储或列存储方式,因此也不适用上述的算法。
- 日志(Log)。日志中需要保存和压缩操作相关的信息,以保证数据的一致性。
- 备份与恢复。在备份与恢复操作时,需要进行相应的数据压缩和解压缩处理。
压缩相关的命令
虽然压缩涉及非常复杂的数据库内部机制,但理论上来说,压缩后的数据库对于使用者是透明的,所有的压缩和解压缩过程都隐藏在数据库内部。因此,在绝大部分情况下,使用者不需要进行额外的操作,甚至不需要知道数据库是否已经被压缩过。
当然,仍然有一些与特定的压缩相关的数据库命令。下面以DB2 V9.7为例,作一简单讨论。
当创建一个表的时候,可指定该表使用压缩。语法如下:
…
) COMPRESS YES;
这条命令创建了一个名为CUSTOMER的表,并且在此表上使用压缩功能。以后在此表上进行的查询、插入、更新等操作,都会自动在后台执行相应的压缩和解压缩操作,而不需要使用特殊的语法。
对于一个已经存在的表,可以用ALTER TABLE命令来启用或停止其压缩功能:
这条命令会在已存在的表CUSTOMER上启用压缩功能。
则会关闭压缩功能。
需要注意的是,使用ALTER TABLE命令启用或停止其压缩功能后,并不会对已经存在的记录生效。举例来说,在对某个表启动压缩后,新插入的(包括被批量load的)记录以及被更新的记录都会以被压缩的格式保存,但是,对于那些已经存在、并且没有在压缩功能启用后被更新过的记录,它们仍然以原始的格式保存。这说明了为什么在启用压缩以后,数据库占有的磁盘空间并没有立即减少。
那么,怎样使压缩功能对于已经存在的记录也生效呢?答案是使用REORG整理整个表。如上例,在执行完ALTER TABLE命令以后,立即执行以下命令:
这条命令将扫描整个CUSTOMER表,并对所有的记录都进行压缩或解压缩。根据表的大小,可能会执行相当长的时间。
压缩率
即使在相同的压缩算法下,表的压缩率也是不固定的。与常规的文件压缩相比,表的压缩率取决于更多的因素。
数据冗余度。数据库压缩的前提是被压缩的数据是冗余的。对于冗余度大的数据,往往能得到很好的压缩率。而对于冗余度很小甚至趋近于0的数据,再好的压缩算法也无济于事。不同数据库的冗余度差别是非常大的。
数据分布。某些类型的压缩算法会在一个小范围内构建压缩字典。这意味在某个较小的范围内,如果可以找到较多的Pattern,就可以获得较高的压缩率。如果Pattern是均匀分布在整个表中的,那么小范围内的字典将效率很低。在表的某些列上建立聚集索引,强制使内容相似的行处于相邻的位置,往往是提高压缩率的一种有效方式。
数据库群集。在群集的情况下,由于字典无法跨越群集,因此必须尽量使单一节点上的数据冗余度尽可能地高。对于DB2来说,精心设计DPF中的Hash Columns,以及配合使用聚集索引,能够有效地影响到压缩率。
某些基于真实应用的数据库的测试表明,压缩技术往往可以将数据库的大小减少70%甚至更多,同时对系统的整体性能没有明显的影响(在I/O密集的系统中,系统性能甚至可能提高)。当然,这个测试结果仅供参考,因为实际的表现依赖于数据库的具体情况。
结论
前面探讨了关于数据库可用性和扩展性方面的问题,我们看到每种产品和架构都是有缺陷的,其实架构就是有所取舍的过程,目标是用最小的代价去解决问题。所以找到适合自己的产品和架构,这才是最重要的。