Designing Data-Intensive Applications 简读摘要——第二章,数据模型和查询语言(part1)

总叙

数据模型可以说是开发软件中最重要的东西,因为他不仅仅影响我们的程序如何编写,更重要的是他影响你如何去思考解决问题的方法

绝大多数的应用都是分层构建数据模型的,对于某一层来说,最关键的问题就是如果用更底层的数据模型表达这个事物。举个例子

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也支持查询关系,但是速度会比较慢

目前看来,两种数据库的边界逐渐变得模糊不清,这对开发者来说是一个好事。结合两种数据库的优点也是未来的发展之道。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,504评论 4 365
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,898评论 1 300
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,218评论 0 248
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,322评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,693评论 3 290
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,812评论 1 223
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,010评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,747评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,476评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,700评论 2 251
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,190评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,541评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,206评论 3 240
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,129评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,903评论 0 199
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,894评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,748评论 2 274

推荐阅读更多精彩内容

  • //我所经历的大数据平台发展史(三):互联网时代 • 上篇http://www.infoq.com/cn/arti...
    葡萄喃喃呓语阅读 51,033评论 10 200
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,105评论 18 139
  • 当前,随着移动互联网的迅猛发展,成熟的PHP开发语言已经被全球广泛应用于网站前端开发当中。2016到现在年,互联网...
    摩洛哥的日光阅读 370评论 0 0
  • 人生酒千杯,千杯少的不仅仅是酒逢知己,还有的遇见青春,遇见故事时说好的千杯不醉,第一杯却已经是酩酊大醉。 第一杯,...
    堕落天使123阅读 403评论 0 0
  • 今夜有磅礴的雨 一遍一遍地把这尘世冲刷 那些肮脏的角落,那些不干净的东西 像马桶上的污垢被清理 今夜,我在一场暴雨...
    朕梁栋阅读 169评论 2 0