《设计数据密集型应用/DDIA》精要翻译(二) :数据模型和查询语言
当我们在设计kafka、mysql这样的数据密集型应用时,数据模型也许是最为重要的一个考虑点。因为它不仅影响了代码的写法,还影响着我们解决问题的思维方式。这是第一章第二节的读书笔记,介绍了几种不同的数据模型以及数据查询语言。
(其实看目录就知道前两章都是小打小闹热热身,但是既然是神书,还是要好好看不是么?)
一、关系型模型与文档型模型的比较
关系型数据库和文档型数据库有很多的差异,包括他们的容错性(本书第五章会讲)、并发处理(第七章会讲)。在这里,我们只关注它们数据模型的不同。
1.哪种模型能让应用层的代码更简单
- 如果应用的数据是一个文档型结构(比如说,有“一对多”的关系,只要加载一次就把需要的全部数据拿到),那用文档型数据库也许是不错的注意
- 文档型数据库也有限制: 比如,我们没办法直接指向一个嵌套在文档中的对象, 而要做类似“列表中的第二条记录的user字段”这样的操作
- 文档型数据库对于“多对一”和“多对多”的支持比较差, 虽说可以在应用层做多次查询数据库的操作来模仿“join”, 但是这样代码会更复杂
- 哪种模型更简单取决于应用本身数据间的关系。对于互相联通的数据,文档型模型就不太好办了, 关系型模型还凑活,最适合的是图模型 (下文会讲到。)
2. 文档型模型的数据格式灵活性
文档性数据库对于数据格式是scheme-free的, 只有在读取数据的时候才会在代码中隐式地表明数据格式,因此也可以叫”scheme-on-read”。而关系型数据库则是”scheme-on-write”, 即往里面写数据的时候就已经显式地知道数据格式了。
在需要改变数据格式的时候,文档型模型只需要直接改代码就可以了。而关系型模型要做alter操作,对于大数据量的表而言,这往往需要花很多的时间,这也是其被诟病的地方。
3. 数据局部性
一个文档通常是存成一个连续的字符串,如果应用需要常常把整个文档读出来,那这种存储方式就对性能有很大帮助(减少了磁盘寻址)。
但是,下面的几个性能方面的让文档型模型的适用场景变少了:
- 就算只是想获取文档的一小部分,也还是会把整个读出来, 浪费了磁盘IO
- 对于文档的更新,会整个重写
- 如果更新后的文档的大小比原来更大,那更新的性能会更差
4.关系型和文档型的交集
目前,很多关系型数据库正在渐渐支持文档型结构,文档型数据库也在支持join等操作。在未来,关系型和文档型模型的混合是数据库可以走的一个方向。
二、数据查询语言
声明式语言和命令式语言
- 命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。(比如用JS去控制网页元素的样式)
- 声明式编程:告诉“机器”你想要的是什么(what),而不是准确地说如何去做(how)。(比如CSS)
对于数据库而言, 声明式查询语言隐藏了实现细节,之后优化时,用户不需要做任何更改;而命令式语言往往需要详细标明所需要的操作。
这一节的结论就是在数据库的范畴里,声明式语言更好。
三、图数据模型
当你的数据中有很多“多对多”的关系时,怎么个搞法? 关系型数据库可以解决一些简单的多对多关系,但是当这种关系变得更复杂时,我们就应该考虑使用图数据模型。图由两种对象组成: 点和边。现实有很多种数据可以用图来建模,比如社交好友关系、web网站(网站是点,link是边)、交通网络等。
在这节中,我们会用以下这个例子来讲述图模型:来自Idaho的Lucy和来自Beaune的Alain结婚了,然后他们一起住在了London。
在图模型中,有几种不同的、但是互相联系的方式来结构化和查询数据。下面我们会讨论 一部分图的存储模型和查询语言。
1.Property Graphs模型
在Property Graphs模型中,每个点由以下内容组成:
- 一个唯一标识符
- 一个出边的集合
- 一个入边的集合
- 一个键值对的集合
每条边由以下内容组成:
- 一个唯一标识符
- 起始点
- 终点
- 一个描述两点之间关系的标签(通过不同的标签,可以在一个图中存储几种不同的信息,而且它们用的都是同一个数据模型)
- 一个键值对的集合
这种图的存储可以用两个关系型数据库的表:
CREATE TABLE vertices (
vertex_id integerPRIMARYKEY, properties json
);
CREATE TABLE edges (
edge_id integer PRIMARY KEY,
tail_vertex integer REFERENCES vertices (vertex_id),
head_vertex integer REFERENCES vertices (vertex_id),
label text,
properties json
);
CREATE INDEX edges_tails ON edges (tail_vertex);
CREATE INDEX edges_heads ON edges (head_vertex);
对于上述例子中的图, 每种点和边都可以使用这个模型,而且,就算之后加了新的feature(比如生了个娃),也不用改模型。
我的ps:
这里只是书上举个例子,没有必要用关系型数据库用存图模型,如果查询需求变得很复杂,SQL会疯掉的!现在有很多直接用图的形式来存储的图数据库,比如Neo4j。(关于这种数据库的存储结构,可以网上搜搜文章,有很多讲解)
2.Cypher查询语言
Cypher是对Property Graphs的一种声明式查询语言。具体语法就不记了。大概就是说,不用操心具体是怎么做的查询,只需要声明自己要什么就行了。
3.SQL中的图查询
上面我们说到,可以用关系型数据库来存储图模型,但是如果要用SQL来做查询就比较蛋疼了,同样的查询任务,用Neo4j + Cypher就4行,但是用SQL要写30行(书上的一个例子,各种join)。再次说明,对于不同的应用需求设计不同的数据模型、采用不同存储方案的重要性。
4.别的模型和查询语言
Triple-Stores模型、SPARQL语言、Datalog语言等等在这里就不细说了,大同小异。
四、总结
纵观历史,数据最早是以树的形式呈现的(垂直模型),但是这种模型不太适用于表示“多对多”的关系,于是人们发明了关系型模型来解决这个问题。再后来,开发者发现这种模型也不太适用于某些应用,于是NoSQL又被发明,分为两个方向:
- 数据是自包含的,数据和数据之间的联系很少(文档型数据库)
- 图数据库恰恰相反,任何数据都有可能和别的一切发生联系
这三种模型在今天都被广泛使用,各自都在特定的领域做的不错。其中的某个模型可以被别的模型所模仿(比如,用关系型数据库来表示图),但是结果往往很糟糕。这也就是为什么不同的系统有不用的设计,没有万金油解决方案。
文档型数据库和图数据库有一个共同点式他们不会强制数据的scheme,这让它们很容易就为变更的需求做调整。但是,你的应用很有可能仍然假设数据是有一个特定的结构的,只是显式(enforced on write)和隐式(handled on read)的区别而已。
尽管我们提了很多东西,但是仍然有一些数据模型我们没有提到:
- 对于基因数据而言,常常需要做序列相似性查询,这意味着我们需要拿一个非常长的字符串与一个超大的字符串数据库做比对。上述没有一种模型可以满足这个需求,因此研究人员写了像GenBank这样的基因组数据库。
- 全文搜索也可以认为是一种常见的数据模型,我们不会过多的讨论它,但是在第三章和Part 3我们会讲到它的查询索引。