大多数应用程序都是一层层的叠加数据模型,每一层设计的关键是如何用于下一层数据模型的表示
例如:
- 程序需要表示用户信息,交易订单等数据结构
- 这些应用的数据结构可以使用json,xml等通用数据格式
- 数据库工程师决定用什么方式表示上述json,xml等数据
- 硬件工程师用电流或光脉冲来表示上一层的字节流
关系模型与文档模型
最著名的是SQL,数据组织为关系,被称为表,每一个关系都是一个无序元组,成为一行
关系数据库的给核心在于商业数据处理,例如事务处理(订票,购物交易)和批处理(打印流水,工资单)
NOSQL的诞生
NOSQL的意思是不仅仅是SQL
NOSQL有如下的优点:
- 扩展性更好,支持大数据集和超高吞吐量
- 普遍都是免费且开源的而不是商业数据库产品
- 关系模型不能很好的支持一些特定的查询操作
- 是一种更具同态和表达力的数据模型
现在关系模型已经可以和非关系数据存储混合使用
对象---关系不匹配
当对象和关系之间存在一对多的关系时,有如下的方案:
- 在传统SQL中可以将user放在一张表,职位经历,教育经历等单独存在其他表,通过外键引用,如下图
- 新的SQL标准增加了对结构化数据类型和XML数据的支持,可以将多值数据存在单行内
- 将工作经历,教育经历等合并成一个大文档,当作一个文本字段列存储,缺点是通常不能查询该列中的值,因为合并成了一个大文档没法查询。市场上有一些面向文档的数据库,例如MongoDB,RethinkDB,CouchDB等,存储为一个文档的好处就是只需要查询一次,不需要连接查询多次
下面的例子将简历表示为一个文档
{
"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"
}
}
多对一和多对多的关系
多对一的例子有很多人生活在一个城市,在一个工厂上班
在存储一些地区或是工厂时可以存储一个id,而不是直接存储名字字符串。使用id的好处是,因为id对于用户没有意义,只对数据库有意义,所以不需要直接改变,即使对应的内容变化了,不需要逐条记录的改变,只需要改变id对应的映射内容
多对多指的是一个求职者可以有很多推荐人,一个推荐人可以推荐多个求职者
文档数据库到底合理吗
用json来表示数据的文档数据库和最初的层次结构相似,层次结构将数据表示为一个有层级的树,支持一对多关系,但是支持多对多困难,并且不支持连接,因为所有的东西都放一起了,所以程序员需要多次查询,手动解析数据间的关系
为了解决这些问题出现了关系模型和网络模型,网络模型最终被淘汰
网络模型是层次模型的推广,层次模型一个节点只能有一个父节点,而网络模型可以有多个,网络模型用指针来表示不同记录间的关系而不是用外键。访问记录需要从根记录开始,沿着指针逐步访问,就像在一个n维空间进行遍历,查询和更新的性能很差
关系模型使用外键来充当访问路径,每次选择不同的外键就是在选择不同的路径,区别在于底层的细节都有查询优化器帮你做了,不需要程序员手动构建访问路径
关系数据库和文档数据的比较
这里主要关注数据模型的差异
文档数据的优点主要是由于局部性,可以查一次就拿到所有的数据,对于某一些应用更接近于应用定义的数据结构
关系数据库优点是连接操作,对多对一和多对多的支持更好
那种模式的应用代码更简单?
如果一次就要使用整个数据树,那么文档模式更简单,如果使用多对多关系,那么使用关系模式更好。对于高度关联的数据,也是关系模式,但是最自然的是使用图模型
文档模型的模式灵活性
文档数据库被称为无模式或者读时模式,读时模式是指应用程序在写入数据库时不做任何的检查,用户可以写入任何样子的数据,例如你可以在json或者xml中包含了不同的字段
关系数据库被称为写时模式,写时模式是指应用程序再写入数据库时会做检查,例如SQL会强制要求你按照定义的表的规范写入
如果一个数据对象集合中的每一个项都有着不同的结构,或是它的结构会动态变化,那么这种时候固定的模式反而是不好的,无模式的文档显得更加的自然
数据查询语言
SQL是一种声明式的语言,只需要指定数据模式,结构需要满足什么条件等,不需要提供具体的实现,查询优化器会决定如何实现,此外它对外隐藏了具体的实现细节,并且不依赖于顺序,适合并行执行
另一类的查询语言叫做命令式,命令式的查询需要指明计算机需要如何操作,例如以下的查询:
function getSharks(){
var sharks = []
for(var i = 0; i < animals.len; i++){
if animals[i].family == "Sharks"{
sharks.push(animals[i])
}
}
return sharks;
}
所以对于直观的感受,声明式和命令式的区别就是:声明式需要说清楚我想干什么,但是具体怎么做不需要你自己当心,而命令式不需要你直接说你想干什么,你只需要告诉计算机怎么做就可以了
还有一个例子可以非常直观的感受声明式和命令式的差别,假设有个海洋动物的网站,用户在查看鲨鱼的界面,想把用户当前浏览的地方高亮显式
<ul>
<li class="selected">
<p>Sharks</p>
<ul>
<li>Great White Shark</li>
<li>Tiger Shark</li>
<li>Hammerhead Shark</li>
</ul>
</li>
<li><p>Whales</p>
<ul>
<li>Blue Whale</li>
<li>Humpback Whale</li>
<li>Fin Whale</li>
</ul>
</li>
</ul>
如果是声明式,也就是我们熟悉的CSS,只需要说清楚想把selected的li标签下的p变成蓝色背景,具体实现交给浏览器,代码就很简单
li.selected > p {
background-color: blue;
}
但是如果是命令式,那就麻烦了,例如想要通过js的DOM来实现:
var liElements = document.getElementsByTagName("li");
for (var i = 0; i < liElements.length; i++) {
if (liElements[i].className === "selected") {
var children = liElements[i].childNodes;
for (var j = 0; j < children.length; j++) {
var child = children[j];
if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "P") {
child.setAttribute("style", "background-color: blue");
}
}
}
}
图状数据模型
如果数据之间的关系大多都是多对多关系,那么使用图状数据模型会更加自然
一个图有两个元素:顶点和边
图状数据模型的优势是提供了单个数据存储区中保存完全不同类型对象的一致性方式
属性图模型
在一个属性图中,每个定点包括:
- 唯一标识符
- 出边集合
- 入边集合
- 属性集合
每个边包括:
- 唯一标识符
- 开始顶点
- 结束顶点
- 描述两个顶点间关系类型的标签
- 属性集合
可以用两张表,一张存储点,一张存储 边。例如,Postgresql对图的实现如下:
CREATE TABLE vertices (
vertex_id INTEGER PRIMARY KEY,
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);
三元存储模型
在三元存储模型中,信息以三元组(主体,谓语,客体)组成
三元组的主体相当于顶点,客体是以下两种情况之一:
- 原始数据类型的值,这样的情况下三元组的谓语和客体对应着主题的一个属性kv对,例如(lucy,age,33)
- 图中的另一个顶点,此时谓语是图中的边,例如(lucy, marriedTo, alain)
一个例子如下:
@prefix : <urn:example:>.
_:lucy a :Person.
_:lucy :name "Lucy".
_:lucy :bornIn _:idaho.
_:idaho a :Location.
_:idaho :name "Idaho".
_:idaho :type "state".
_:idaho :within _:usa.
_:usa a :Location
_:usa :name "United States"
_:usa :type "country".
_:usa :within _:namerica.
_:namerica a :Location
_:namerica :name "North America"
_:namerica :type :"continent"
因为_:lucy的主题写了很多次很麻烦,可以把它们写在一起:
@prefix : <urn:example:>.
_:lucy a :Person; :name "Lucy"; :bornIn _:idaho.
_:idaho a :Location; :name "Idaho"; :type "state"; :within _:usa
_:usa a :Loaction; :name "United States"; :type "country"; :within _:namerica.
_:namerica a :Location; :name "North America"; :type "continent".
最后有一个问题:图数据模型和网络模型有区别吗
其实是有的区别如下:
- 网络模型是有模式限制的,例如在一条记录上,第一个节点是Person类型,第二个节点一定是Location类型,而图模型可以是任何的类型
- 网络模型获取一个记录的唯一方法是遍历,而图模型可以根据唯一id直接找到这个顶点
总结一下,第二章介绍了不同的数据模型已经对应的数据查询语言
最初的数据被表示为层次模型,但是不利于多对多,于是出现了关系模型,又因为一些应用不适合关系模型,出现了NoSQL,NoSQL主要分为两种:文档数据库主要用于数据之间的联系很少,图数据库则恰恰相反。事实上,一种模型可以用另一种模型实现,例如用两张表实现图数据模型。文档数据模型和图数据模型的共同点是都不会对数据施加某种模式,具体对数据的解释留给了应用程序,增加了灵活性,这其实是一种隐式的模式(读时处理),另外一种SQL则是显式的模式(写时强制)