2.4 关系型数据库的不足
关系型数据库有许多优势,但绝非完美,在诞生之初就有很多令人不满意的地方。作为通用型的数据库,其性能是非常高的,然而,正是由于通用性好,使它并不能完全适应所有的应用需要,在大量数据的写入操作、对海量数据高效存储和访问、为有数据更新的表做索引或表结构(schema)变更、字段不固定时的应用、对简单查询需要快速返回结果的处理等方面不擅长。
2.4.1 大量数据的写入操作
随着互联网业务模式的兴起,以及高度分布式、全球化、“永不间断”应用程序的涌现,事务处理系统面临新的要求。我们知道,在Web 1.0时代,互联网内容是由少数编辑人员(或站长)定制的(比如各门户网站),互联网是“阅读式互联网”。在Web 2.0时代,每个人都是内容的提供者,又是内容的消费者,互联网是“可写可读互联网”。Web 2.0包含了我们经常使用到的服务,例如简易信息聚合(Really Simple Syndication,RSS)、博客、播客、维基、对等计算(Peer to Peer,P2P)下载、社会书签、社会性网络服务(Social Networking Services,SNS)、社区、分享服务、微信等(如图2-1所示)。Web2.0网站是根据用户个性化信息来实时生成动态页面和提供动态信息,基本上无法使用动态页面静态化技术,因此数据库并发负载非常高,大型网站常常遇到每秒上万次读/写请求。
在互联网应用中,不仅写入数据库的次数频繁,而且写入的数据量也可能非常大。经典的关系数据模型并没有对数据容量有明确的限制,但现实环境下用户却不得不考虑数据容量的限制问题。借助各种信息平台,掩盖在互联网下面的海量用户无时无刻不在制造或者拼凑着大量的信息,这些信息可能小到推特(Twitter)上的一句“Hi!”,也可能大到一张蓝光电影。用关系型数据库来处理它们,对数据库的写入能力提出了严峻的挑战。
传统的关系型数据库在数据读入方面常常用由复制产生的主从模式(数据的写入由主数据库负责,数据的读入由从数据库负责),人们可以比较简单地通过增加从数据库来实现规模化。在数据的写入方面,其规模化问题完全没有简单的方法来解决。于是,有人提出将主数据库从一台增加到两台,作为互相关联复制的二元主数据库使用,以此来解决写入的规模化问题。这种方案似乎可以把每台主数据库的负荷减少一半,但这种方案也带来了更新处理的难题,可能会造成数据的不一致(同样的数据在两台服务器上同时更新时会出现不同的结果)。为了避免冲突,需要把对每个表的请求分别分配给合适的主数据库来处理,这样就带来了系统的复杂性。图2-2所示为两台主机问题及解决办法。
图2-1 Web 1.0与Web 2.0读/写差异示意图
图2-2 两台主机问题及解决方案
有人试图以适当的方式将数据库分割开来(如图2-3所示),在控制访问频率和数据量方面进行优化,实践证明,这种方法在一定程度上可以解决这个问题。在大规模环境下使用关系型数据库,一般采用水平分割和垂直分割两种分割方式。水平分割就是将一张表中的各行数据直接分割到多个表中。例如,对于像日本Mixi这样的SNS社交网络服务网站,如果将编号为奇数的用户信息和编号为偶数的用户信息分别放在两张表中,应该会比较有效。垂直分割就是将一张表中的某些字段(列)分离到其他的表中。如果用SNS网站举例,相当于按照“日记”、“社区”等功能对数据库进行分割。通过这样分割,可以对单独一个关系型数据库的访问量和数据量进行控制。然而,一旦真正这样做,相应的系统维护的难度也会随之增加。
图2-3 数据库分割示意图
这一改变将会导致分别存储在不同服务器上的表之间无法进行连接(join)处理,在进行数据库分割的时候用户不得不预先考虑这些问题。当然,如果一定要进行连接处理,可以在程序中进行关联,显然这是非常困难的。
2.4.2 对海量数据的高效存储和访问
传统的关系数据库在应付Web 2.0可读可写网站时,特别是超大规模和高并发的社交网络类型的Web 2.0纯动态网站已经显得力不从心。在“脸谱”(Facebook)这个全球最大的社交网上,以分享照片为例,“脸谱”用户上传照片数超过15000000000张,为方便使用,每张照片上传后会在后台被处理成4种尺寸,因此照片存储数量高达600亿张,总容量超过1.5PB,同时,每周新上传的照片多达两亿张,存储需要约1.5TB的容量,在高负载情况下,Facebook每秒需要处理获取照片请求多达55万次。类似Facebook、Twitter、Friendfeed这样的SNS网站,每天都产生海量的用户动态,以Friendfeed为例,一个月就达到了2.5亿条用户动态,对于关系型数据库来说,要在一张上亿条记录的表里面进行SQL查询,效率是极低的,甚至是令人无法忍受的。传统事务管理技术ACID特性致力于确保数据的完整性和一致性,并追求查询结构的严格正确,在海量数据管理中显得不太适用。
2.4.3 为有数据更新的表做索引或表结构变更
使用关系型数据库,为了加快查询速度需要创建表索引,要增加一些字段就必须改变表的结构。为了进行这些处理,需要对表进行共享锁定,在这期间数据的更新、插入、删除等变更是无法进行的。如果需要进行一些耗时的操作(如为数据量比较大的表创建索引或者变更其表结构),需要特别注意:长时间内数据可能无法进行更新。表2-11所示的是共享锁和排他锁。
表2-11 共享锁和排他锁
2.4.4 对简单查询需要快速返回结果的处理
关系型数据库并不擅长对没有复杂查询条件的简单查询快速返回结果的处理。因为关系型数据库是使用专门的结构化查询语言(SQL)进行数据读取的,它需要对SQL语言进行解析,同时还有对表的锁定和解锁这些必需的额外开销。这种情形,在Web 2.0时代显现出不足。比如大型Web网站的用户登录系统,例如腾讯、盛大,动辄数以亿计的账号,传统的关系型数据库难以应付。除此之外,在互联网海量数据管理的众多应用中,有相当一部分应用人们关注的重点是查询的时间,而并不在乎查询结果的百分之百正确。因此,时效性成为了关键。
2.4.5 字段不固定时的应用
关系型数据库假定数据的结构已明确定义,数据是致密的,并且在很大程度上是一致的。关系型数据库构建在这样的先决条件上,即数据的属性可以预先定义好,它们之间的相互关系非常稳固且被系统地引用(systematically referenced)。它还假定定义在数据上的索引能保持一致性,能统一应用以提高查询的速度。然而,一旦这些假设无法成立,关系型数据库就立刻暴露出问题。当然,关系型数据库可以容忍一定程度上的不规律和结构缺乏,但在松散结构的海量稀疏数据面前,关系型数据库就立刻暴露出的问题。在大规模数据面前,传统存储机制和访问方法显现出很大的局限性。
在关系型数据库中,如果表中的字段不固定,那么将是一件比较麻烦的事。当然,在需要的时候可以为表添加新的字段,但这种方法在实际应用中有很大的局限性。另一种解决方法是为表预留足够多的字段,但随着时间的流逝,这种方法很容易让人弄错字段的含义,以及哪些字段保存了哪些数据。所以,这种方法并不是很好的方法。