在今天,最著名的数据模型莫过于SQL了吧。这是一个Codd在1970年提出的关系模型:数据被组织成关系(称为SQL中的表),其中每个关系都是无序的元组集合(SQL中的行)。
关系模型是一个理论上的设想,在当时很多人都怀疑它是否能有效地实施。然而,到上世纪80年代中期,关系数据库管理系统和SQL已经成为大多数需要使用某种常规结构来存储和查询数据的人的首选工具,关系数据库的主导地位也持续了25年之久。
关系数据库的根源在于业务数据处理,在20世纪60年代和70年代这些其实是在大型计算机上执行的。而从今天的角度来看,用例看起来也十分普通:典型的事务处理(进入销售或银行事务、航空公司预订、仓库中的库存)和批处理(客户发票、工资、报表)。当时的其他数据库还在迫使应用程序开发人员考虑数据库中数据的内部表示,而关系模型的目标却是在一个更干净的接口后面隐藏实现细节。
多年来,人们都在比较不同的数据存储和查询的方法。比如,在20世纪70年代和80年代早期,网络模型和层次模型是主要的替代方案,但是关系模型开始主导它们。在20世纪80年代末和90年代初,对象数据库再次出现。而XML数据库出现在21世纪初,但也只看到了特定的应用。关系模型的每个竞争对手都在其时代产生了大量的炒作,但从未持续过2次。
随着计算机变得更加强大和网络化,它们开始被用于日益多样化的用途。值得注意的是,关系数据库在最初的业务数据处理范围之外,开始被广泛应用于各种用例。今天在web上看到的大部分内容仍然由关系数据库驱动,比如在线发布、讨论、社交网络、电子商务、游戏、SAAS应用程序,甚至更多。
NOSQL的诞生
而现在,NoSQL是颠覆关系模式主导地位的最新尝试。“NoSQL”这个名字很不幸,因为它实际上并不指代任何一种特定的技术——它最初只是作为一个吸引人的Twitter标签,用在2009年的一场关于开源分布式非关系型数据库的meetup。然而,这个术语引起了很多人的关注,并迅速在网络创业社区和其他地方传播开来。现在,许多有趣的数据库系统都与nosql关联在一起,而且它还被重新解释为不仅仅是SQL。
采用NoSQL数据库的背后有几个驱动因素,主要包括:
1.大家需要一个相比于关系数据库来说,具有更大的可伸缩性,包括非常大的数据集或非常高的写入吞吐量的系统。
2.相比于商业数据库产品,人们当然更偏爱于免费和开源软件
3.一些特殊的查询操作,这是关系模型没有很好支持的
4.对关系模型限制性的失望,以及对更动态、更富表现力的数据模型的渴望
不同的应用程序有不同的需求,对于一个用例来说,最好的技术选择可能与另一个用例的最佳选择是不同的。因此,在可预见的将来,关系数据库将继续与各种非关系型数据存储一起使用。
对象-关系不匹配
在当今,大多数应用程序开发都是用面向对象的编程语言完成的,这导致了对SQL数据模型的常见批评:如果数据存储在关系表中,应用程序代码中的对象和表、行和列的数据库模型之间需要一个笨拙的转换层。模型之间的脱节有时被称为阻抗失配。
像Hibernate这样的对象-关系映射(ORM)框架减少了这个翻译层所需的样板代码的数量,但是它们不能完全隐藏这两个模型之间的差异。
例如,图2-1演示了如何在关系模式中表达简历(LinkedIn个人资料)。整个概要文件可以通过唯一标识符userid来标识。像firstname和lastname这样的字段在每个用户中出现一次,因此它们可以被建模为用户表中的列。然而,大多数人的职业生涯(职位)都不止一份工作,而且人们的受教育年限和接触信息的数量也各不相同。由此可见,这是一个一对多的关系,我们也可以用不同的方式表示:
1.在传统SQL模型中(SQL1999),最常见的表达方式是将位置、教育、和联系信息存在单独的表中,然后跟用户表建立外键引用,如图2-1所示。
2.后来的SQL标准版本增加了对结构化数据类型和XML数据的支持;这使得多值数据可以被存储在一行中,并支持在这些文档中查询和索引。这些特性在Oracle、IBM DB2、SQL Server和PostgreSQL中都得到了不同程度的支持。JSON数据类型也受到几个数据库的支持,包括IBM DB2、MySQL和PostgreSQL。
3.第三种选择是将工作、教育和联系信息转化成JSON或XML文档,将其存储在数据库的文本列中,并让应用程序解释其结构和内容。在这种设置中,通常不能使用数据库直接查询列内的值。
图2-1
对于像简历这样的数据结构来说,它是一个自成体系的文档,所以用JSON表示非常合适:参见例2-1。JSON的表达比XML要简单得多。面向文档的数据库,如MongoDB、RethinkDB、CouchDB和Espresso,都支持这种数据模型。
例2-1,将LinkedIn个人资料用一个JSON表示:
{ "user_id": 251, "first_name": "Bill", "last_name": "Gates", "summary": "Co-chair of the Bill & Melinda Gates... Active blogger.", "region_id": "us:91", "industry_id": 131, "photo_url": "/p/7/000/253/05b/308dd6e.jpg", "positions": [ {"job_title": "Co-chair", "organization": "Bill & Melinda Gates Foundation"}, {"job_title": "Co-founder, Chairman", "organization": "Microsoft"} ], "education": [ {"school_name": "Harvard University", "start": 1973, "end": 1975}, {"school_name": "Lakeside School, Seattle", "start": null, "end": null} ], "contact_info": { "blog": "http://thegatesnotes.com", "twitter": "http://twitter.com/BillGates" }}
一些开发人员认为JSON模型减少了应用程序代码和存储层之间的阻抗失配。然而,正如我们将在第4章中看到的,JSON作为一种数据编码格式也存在问题。缺乏schema通常被认为是一种优势,我们将在后续“文档模型的模式灵活性”中讨论这一点。
与图2-1中的多表模式相比,JSON很明显更加适合。如果您想要在关系示例中获取profile,您需要执行多条查询(通过userid查询每个表),或者在users表及其下属表之间的做join。在JSON表示中,所有相关的信息都在一个地方,一个查询就足够了。
从用户profile到用户的位置、教育历史和联系信息的一对多关系,我们可以看出这是数据中的树结构,而JSON能够显示的表达此树结构(参见图2-2)。
图2-2
多对一和多对多关系
在前一节中,以2-1为例,区域id和行业id被作为id,而不是纯文本字符串“大西雅图地区”和“慈善”。为什么?
如果用户界面有用于输入区域和行业字段的文本框,那么将它们存储为纯文本字符串是有意义的。但是,拥有一个标准化的地理区域和行业列表,并让用户从下拉列表或自动完成列表中进行选择是有一定的优势的,具体如下:
1.一致的风格和拼写
2.避免模棱两可(例如,如果有几个同名的城市)
3.易于更新——名称只存储在一个地方,因此,如果需要更改的话,它很容易进行更新(例如,由于政治事件而更改城市名称)。
4.本地化支持——当站点被翻译成其他语言时,标准化的列表可以本地化,因此区域和行业可以浏览者的本地语言来显示。
5.更好的搜索。
是否存储ID或文本字符串是一个关于重复的问题。当您使用一个ID时,对人类有意义的信息(例如慈善这个词)只存储在一个地方,并且所有引用它的信息都使用一个ID(它只在数据库中有意义)。当您直接存储文本时,您将在使用它的每条记录中复制有意义的信息。
使用ID的优点是,由于它对人没有意义,所以它永远不需要改变:ID可以保持不变,即使它标识的信息发生了变化。任何对人类有意义的事情都需要在将来的某个时候改变——如果这些信息是重复的,那么所有多余的副本都需要更新。这就会导致写管理费用,并存在不一致的风险(一些信息的副本会被更新,而其他的则不会)。消除这种重复是数据库中标准化背后的关键思想。
不幸的是,规范化这种数据需要多对一关系(许多人生活在一个特定的区域,许多人在一个特定的行业工作),这与文档模型不太匹配。在关系型数据库中,用ID引用其他表中的行是很正常的,因为join很容易。在文档数据库中,通常不需要对一对多的树结构进行join,而且对join的支持常常是弱的。
如果数据库本身不支持join,那么您必须通过对数据库进行多次查询来模拟应用程序代码中的join。(在这种情况下,区域和行业的列表可能很小,而且变化很慢,因此应用程序可以简单地将它们保存在内存中。但是,创建join的工作从数据库转移到应用程序代码。)
此外,即使应用程序的初始版本很适合 join-free的文档模型,数据也会随着特性被添加到应用程序中而变得更加相互关联。例如,考虑一下我们可以对简历示例进行一些更改:
组织和学校作为实体
在前面的描述中,组织(用户工作的公司)和学校名称(他们学习的地方)只是字符串。也许它们应该是对实体的引用?然后,每个组织、学校或大学都可以拥有自己的网页(带有徽标、新闻提要等);每个简历都可以链接到它提到的组织和学校,并包含他们的标志和其他信息(见图2-3,来自LinkedIn的一个例子)。
建议
假设您想要添加一个新特性:一个用户可以为另一个用户写推荐信。这些建议在被推荐的用户的简历上显示,并附上推荐用户的姓名和照片。如果推荐者更新他们的照片,他们所写的任何推荐都需要反映新照片。因此,这些建议应该有一个针对作者的个人资料的引用。
图2-3
图2-4说明了这些新特性为什么需要多对多关系。每个点状矩形中的数据可以被分组到一个文档中,但是对组织、学校和其他用户的引用需要被表示为引用,并且在查询时需要join。
图2-4