总叙
数据模型可以说是开发软件中最重要的东西,因为他不仅仅影响我们的程序如何编写,更重要的是他影响你如何去思考解决问题的方法
绝大多数的应用都是分层构建数据模型的,对于某一层来说,最关键的问题就是如果用更底层的数据模型表达这个事物。举个例子
1. 对于一个应用开发者而言,你需要将现实世界中的人、组织、商品、行为、现金流等等东西抽象成一个个对象,或者数据结构。然后开发一系列的API去操作这些对象。这些对象往往和你的应用是紧密相关的。
2. 当你需要存储这些对象的时候,我们把这些与应用紧密相关的数据结构转化成为更加普遍的结构,例如json, xml,关系型数据表,或者一个图
3. 数据库工程师选择一种方法将2中的json, xml等等东西转化成字节,存储到内存或者磁盘中。这种转化方法需要满足我们各种我们查询、检索、管理操作的功能。
4. 在更低的层级, 硬件工程师需要把字节转化成电平高低,光信号,磁场强度等等
在一个复杂的应用中,往往会有很多的中间层,诸如基于某些API开发出来的API,这些层的基本目标是一致的。将底层复杂的逻辑隐藏起来,给上层调用者提供一个简单的数据模型。这种抽象允许不同的人能够在一起更高效工作。举个例子,数据库的抽象API,比如SQL让数据库服务商和数据库开发者都能够同样高效的使用数据库。(个人理解是使用的人不用再关心mysql里面的索引是怎么做的,只需要知道建索引用create index就好了)
数据模型有千千万,每种数据模型就对于如何被使用做了若干**假设约定**,某些功能用起来很爽但某些功能就不支持,某些操作速度奇高但某些就卡成狗,某些数据转换看起来很自然但是某些看着就很别扭。如果他是一个冰箱,你非让他去制冷就废了。所以对于你的应用而言,选择合适的数据模型就变得尤关重要
关系模型 vs 文档模型
当下最经典的数据模型无外乎是SQL数据模型,他将数据以关系的形式进行组织存储在表中,每个表中数据为无序的集合,也就是表中的每一行数据。
关系模型和文档模型的数据库历史就不讲了,简单来说就是80年代开始关系数据库几乎统治整个计算机界,文档数据库大概在2000年左右开始出现,各种对象数据库都在一定程度上挑战了关系数据库的地位,但是时至今日,我们的大量应用仍然以关系型数据库为存储模型
NoSQL出现
NoSQL典故就不讲了,没啥用
导致NoSQL出现的有原因有以下几点
1. 相比传统关系数据库更好的扩展性,能够存储更大的数据集和更大的吞吐量
2. 相比商业软件(也就Oracle了吧),希望从能够利用广泛传播的开源软件中(好改,懂原理)
3. 需要某些传统关系数据库支持欠佳的特定功能
4. 被关系数据库中schema的限制搞得头大,很多时候我不知道数据应该有几列,改又很难,需要一些更灵活的数据模型
由于不同应用的不同需求,所以最终我们会导向一个SQL和各种NoSQL存储并存的一个混合持久方案
关系数据库的问题: 对象与关系的不适配(mismatch)
当下我们很多人都用面向对象语言编程,而当数据存储在关系数据库中,我们需要一个丑陋的ORM层来将关系数据转换成对象,这个东西本身是很丑的,虽然有很多组件,比如hibernate, mybatis在让他没那么丑,但是他还是无法隐藏数据库中的具体存储格式.(至少你很难在不知道数据库表结构的情况下,把查询存储用的很6,就说明他对底层的抽象封装是不够的)
以下图为例, 左边是一个比尔盖茨的简历, 每个人的简历有一个唯一的id(userid),像姓名这种字段每个人都有且只有一个,但是大部分人都有超过一个工作经历,很多条教育经历,以及任意多条联系信息。想表达这么多信息就有很多种方法了。
1. 运用传统关系数据库, 把这些信息存到若干个表中,然后用外键把他们连起来,就好像下图右边那样
2. 目前很多关系数据库都支持json格式,所以可以把一整条数据存到一个数据表中(比如表中有一个字段叫ContactInfo, 里面存[{"blog":"thegatesnotes.com", "twitter":"@BillGates"}])
3. 直白点,直接把所有内容转换成一个xml或者json格式的数据,然后存到数据库中,但是很显然,这样你就没办法数据里面的字段做查询了(比如不支持查1973年在Harvard读书的人)
在这种情况下,其实把整个简历表达成一个json格式的数据就很自然,要比ORM那一套爽很多,格式如下。另外用json格式在查某一个特定简历的时候也很爽,一下就把所有数据都拉出来了,不用去搞什么join
文档数据库的问题:多对多,多对一的关系时,需要存id而不是内容
在上图的数据库设计中,我们看到一个现象,针对region和industry, 表中存储的是对应的id,region_id和industry_id,而不是具体的内容。原因有以下几个方面
1. 在UI层,可以提供用户一个下拉框以供选择,而不是用一个可以随意输入的对话框,防止用户输入错误(用户错误那真是千奇百怪,多一个tab多个回车,还有多一个js代码的……,正常点的就像大小写不一致)
2. 避免歧义,比如同名城市(中国的凤凰城和美国的凤凰城,中国有几十个村子叫红旗村)
3. 便于更新,一旦城市名字更新,只需要更新id到城市名字的映射就可以了,一行搞定
4. 本地化支持, 一个id可以有不同语言的名字, 比如10中文是北京,英文是beijing
5. 更便于检索, id的查询要比text对比快很多,也更准
---
对于你是存id还是存内容,在于你是否想把一个对人类有意义的内容在每条记录中都重复出现?
在关系数据库中,用id的好处是任何有意义的东西都有可能会变,而一旦要变,存id就只改一个映射关系,而存内容可能就要每条记录一条一条改。在实际查找中,用一个简单的join就可以从id拿到实际的内容
但是在文档型数据库中,事情就不太一样了,因为这类数据库对join操作的支持往往很差,应用有时需要自己实现一个join操作,这个就变得很麻烦了
另外,即使在最初的时候,你的应用存在一个文档数据库中存的很好,但是数据往往会变得属性越来越多,彼此之间的联系也越来越多。以刚才的简历为例,我们可能会加一些功能
1. 组织和学校从一个简单的名字变成一个多属性的实体,我们会给他加上logo, 主页等等内容,看图2-3的例子
2. 推荐信, 一个用户可以给另一个用户写推荐信,推荐信跟随简历一同显示,同时会显示推荐者的名字、照片等等内容,当一个推荐者的照片更新时,他推荐的对象的简历上,这个推荐者的照片也要同步更新(我擦,好复杂!!!)
文档数据库会重蹈历史吗?
尽管多对多的关系和join被广泛用到了关系数据库中,关于如何表现这种关系的讨论却很激烈。其实在历史中,有两种解决的方法,网络模型和关系模型。网络模型把每个记录类似一个图当中的一个节点,节点之间的关系由节点之间的边进行存储表达,很像我们现在的图数据库。关系模型其实就是我们目前的关系数据库,就不用多解释了。(我们现在的图数据或者文档数据库库其实有沿袭当年网络模型)
文档数据库 vs 关系数据库
这里我们只对比数据模型上的差异,像容错性,一致性的对比后面再讲
哪种模型会让代码更简单?
简单来说一句话,如果你的数据内部关系很多,那就用关系数据库,如果结构负责且内部关系不复杂,就用文档数据库
如果你的数据结构就属于文档型的(好像简历),那文档数据库就很适合你,你也不需要把很多字段硬拆到每个表里面去,然后查一条数据各种join。文档数据库也有他的问题,你很难基于文档里面的某一个字段做查询,譬如查某个用户第二个职位是什么,(关系数据库可以简单用SQL搞定,但是文档型就得写代码了).别忘了文档数据库对join的支持很差,也就意味着对于多对多的关系表达很差,当然,如果你的应用基本没有这类需求,那就不是问题了。
反过来,如果你的应用需要表达大量的多对多关系,那关系数据库就很爽了,你不会因为某个地方改名了而扫全表去修改,也不需要用代码去实现类似join的操作。
文档数据库的结构(schema)更为灵活
文档型数据库基本上都支持类似json这种格式,经常被称作无结构的,其实更准确的说法是读式结构,也就是说他的结构(schema)是在读取的时候才能确定。这与关系数据库写式结构相反,关系数据库有明确的类型定义,数据在写入的时候就要求满足预定义的格式要求。这就好像动态语言(python)和静态语言(c++)的区别一样,一个变量的类型到底是在执行时才能知道还是预先就已经编译好了
这个区别在改字段的时候差异特别明显,比如我们需要把原先一个字段存姓名改为姓和名分开存。在文档数据库中,你就直接把写的地方改成两个字段,然后在读的时候加一行代码,就搞定了。
if (user && user.name && !user.first_name) {
user.first_name = user.name.split(" ")[0];
}
但是在关系数据库中,事情就有些复杂了
ALTER TABLE users ADD COLUMN first_name text;
UPDATE users SET first_name = substring_index(name, ' ', 1);
因为需要改表结构并且把整个表更新一遍,所以对于无论是mysql也好,还是其他关系数据库也好,这都是一个特别慢的过程。所以还有一种方法是把表改好,first_name 为NULL,在读的时候写把name读出来,解析出姓再写到first_name里,那这个代码就跟文档数据库没什么区别了。相当于你用关系数据库名搞了一个文档数据库的实。
所以如果你遇到已下场景以导致你的数据结构不是都一致的情况下,用一个非结构化的数据库就能让你省去很多麻烦。
1. 不同种类的对象结构不完全相同,但是又不可能每种对象存一个库
2. 写入方不可控,你根本不知道他会写什么进去
但是当你的数据结构一致的时候,有一个结构定义(schema)会对你有帮助,因为这能帮助你规范化数据,也能够帮助你省去写类型校验的代码,当你的数据库为分结构化的时候,各种类型校验,字段校验会散步你的整个代码,让你感觉非常头大的!!!所以就算你要用文档数据库,我也强烈建议用PB之类的东西做序列化而不是用json这种完全随意的格式。因为代码层需要判断一个字段是否存在,他的类型是什么,注意,这种判断是每个字段都要做,搞死了!!
数据本地化
文档数据库好的一面
当我们查一条数据的时候,如果你的应用经常需要你查整个文档数据,比如一整条简历,那把数据存成一整个文档,写进文档数据库要比关系数据库中各种join快很多,因为磁盘读写也好,查索引耗时,很多原因了。
文档数据库渣的一面
但是反过来,如果你经常要查的是整个文档的一部分,那用文档数据库每次把整个数据都拉出来就显得很浪费了。另外再更新的时候,如果你只是更新很少的字段,你也要去写整条数据(这里就遇到可能需要先拉出来整条数据,然后改之后再写进去),就很麻烦了.另外,如果你新写进去的数据比原来数据更大,那数据库可能需要重新在磁盘找一块位置存他,这就更慢了。所以强烈建议你的文档尽可能小,且不要没事就变大。这很影响性能。或者说如果你的数据就是这样,那谨慎考虑用文档数据库。
两种数据库的结合
很多关系数据库(除了Mysql)都已经支持XML,允许你修改XML,查询XML内部的字段以及用某个字段建索引,这就跟你操作文档数据库的方式很像了。另外绝大部分数据库都支持json格式
在文档数据库方面, RethinkDB在查询时支持类似join的操作, Mongodb也支持查询关系,但是速度会比较慢
目前看来,两种数据库的边界逐渐变得模糊不清,这对开发者来说是一个好事。结合两种数据库的优点也是未来的发展之道。