概要
数据模型
大多应用程序通过叠加数据模型构建
- 应用开发者,观测现实世界,进行抽象构建为一组对象、数据结构及操作数据结构的API
- 数据库管理员,采用JSON、xml、表或图模型,进行表示和存储
- 数据库工程师,使用内存、磁盘、网络的字节格式表示上述模型,并支持多种方式的操作
- 硬件工程师,将字节流用电流(内存)、光脉冲(网络)、磁场(磁盘)等表示
关系模型和文档模型
关系模型
当今最流行的关系模型:sql,基于关系模型,将数据组织成关系,在sql中称为表(Table),每个关系称为行(row)
最初核心在于商业数据处理
- 事务处理
-
- 销售和银行交易
- 航空公司订票
- 仓库库存
- 批处理
-
- 客户发票
- 工资单
- 报告
地位举足轻重,即便互联网发展蓬勃,需求日益多样和精细化,其他模型百花齐放,但大多数内容依然由关系型数据库支撑
nosql的诞生
nosql是对不同于传统的关系数据库的数据库管理系统的统称。
诞生原因
- 可处理更大数据集,有超高的写入吞吐量
- 开源免费,传统关系型数据库收费
- 特定的关系型不好支持的查询操作
- 关系模型限制太多,更具动态和表达力的数据模型
对象-关系的不匹配
应用层的对象和数据库的关系型数据模型不直接匹配,往往需要中间层转换(orm框架)
另外,关系模型很难直观表现一对多关系(需要多张表)。比如简历上,一个人可能有多段教育经历和多段工作经历
文档模型:使用 Json 和 XML 的天然嵌套。
关系模型:使用 SQL 模型就得将职位、教育各自创建一张表,然后在用户表中使用外键关联。
这个例子中,文档模型还有几个优势:
- 模式灵活:可以动态增删字段,如工作经历。
- 更好的局部性:一个人的所有属性被集中访问的同时,也被集中存储。
- 结构表达语义:简历与联系信息、教育经历、职业信息等隐含一对多的树状关系可以被 JSON 的树状结构明确表达出来
多对多和多对一的关系
以region为例,存储时采用region_id → region name,其他地方都引用 region_id,而不是直接存储纯字符串:“Greater Seattle Area”
- 统一样式
- 避免同名带来的歧义
- 易于更新,id不动,只改id对应的内容即可
- 本地化支持,翻译成其他语言,可以只翻译这一张表
- 搜索便利,可以关联地区,进行树状的组织
文档型数据库很擅长处理一对多的树形关系,却不擅长处理多对多的图形关系。如果其不支持 Join,则处理多对多关系的复杂度就从数据库侧移动到了应用侧。
如,多个用户可能在同一个组织工作过。如果我们想找出在同一个学校和组织工作过的人,如果数据库不支持 Join,则需要在应用侧进行循环遍历来 Join。
文档 vs 关系
- 对于一对多关系,文档型数据库将嵌套数据放在父节点中,而非单拎出来放另外一张表。
- 对于多对一和多对多关系,本质上,两者都是使用外键(文档引用)进行索引。查询时需要进行 join 或者动态跟随。
文档模型是否在重演历史?
层次模型
20世纪70年代,IBM 的 信息管理系统( Information Management System, IMS )
- 树形组织,但是每个子节点只有一个父节点
- 节点存储数据,有类型
- 节点间通过引用(指针)连接
类似文档模型,同样无法处理多对多关系,也不支持join
为了解决这个问题,提出来两种解决方案:
- 关系模型
- 网络模型
网络模型
是层次模型的推广,一个记录可以有多个父节点,从而支持多对一和多对多。
多对一和多对多都可以由路径来表示。访问记录的唯一方式是顺着元素和链接组成的链路进行访问,这个链路叫访问路径,在 n维空间中进行遍历,难度高。
内存有限,因此需要严格控制遍历路径。并且需要事先知道数据库的拓扑结构,这意味着拓扑改变需要重新写代码进行访问路径的,得针对不同应用写大量的专用代码。
关系模型
在关系模型中,数据被组织成元组(tuples),进而集合成关系(relations);在 SQL 中分别对应行(rows)和表(tables)。
其主要目的和贡献在于提供了一种声明式的描述数据和构建查询的方法。
即,相比网络模型,关系模型的查询语句和执行路径相解耦,查询优化器(Query Optimizer 自动决定执行顺序、要使用的索引),即将逻辑和实现解耦。伟大啊
如果想使用新的方式对数据集进行查询,只需要在新的字段上建立一个索引。那么在查询时,你并不需要改变的你用户代码,查询优化器便会动态的选择可用索引。
文档型和关系型
根据数据类型来选择数据模型
文档型 | 关系型 | |
对应关系 | 数据有天然的一对多、树形嵌套关系,如简历。 | 通过外键 + Join 可以处理 多对一,多对多关系 |
代码简化 | 数据具有文档结构,则文档模型天然合适,用关系模型会使得建模繁琐、访问复杂。但不宜嵌套太深,因为只能手动指定访问路径,或者范围遍历 | 主键,索引,条件过滤 |
Join 支持 | 对 Join 支持的不太好 | 支持的还可以,但 Join 的实现会有很多难点 |
模式灵活性 | 弱 schema,支持动态增加字段 | 强 schema,修改 schema 代价很大 |
访问局部性 | 1. 一次性访问整个文档,较优 | 分散在多个表中 |
对于高度关联的数据集,使用文档型表达比较奇怪,使用关系型可以接受,使用图模型最自然。
文档模型中的模式灵活性
说文档型数据库是 schemaless 不太准确,更贴切的应该是 schema-on-read。
数据模型 | 编程语言 | 性能 & 空间 | ||
schema-on-read | 写入时不校验,而在读取时进行动态解析。 | 弱类型 | 动态,在运行时解析 | 读取时动态解析,性能较差。写入时无法确定类型,无法对齐空间利用率较差。 |
schema-on-write | 写入时校验,数据对齐到 schema | 强类型 | 静态,编译时确定 | 性能和空间使用都较优。 |
文档型数据库使用场景特点:
- 有多种类型的数据,但每个放一张表又不合适。
- 数据类型和结构又外部决定,你没办法控制数据的变化。
查询时的数据局部性
如果你同时需要文档中所有内容,把文档顺序存会效率比较高。
但如果你只需要访问文档中的某些字段,则文档仍需要将文档全部加载出。
但运用这种局部性不局限于文档型数据库。不同的数据库,会针对不同场景,调整数据物理分布以适应常用访问模式的局部性。
- 如 Spanner 中允许表被声明为嵌入到父表中——常见关联内嵌
- HBase 和 Cassandra 使用列族来聚集数据——分析型
- 图数据库中,将点和出边存在一个机器上——图遍历
关系型和文档型的融合
- MySQL 和 PostgreSQL 开始支持 JSON。
原生支持 JSON 可以理解为,MySQL 可以理解 JSON 格式。如 Date 格式一样,可以把某个字段作为 JSON 格式,可以修改其中的某个字段,可以在其中某个字段建立索引。
- RethinkDB 在查询中支持 relational-link Joins
数据模型可以相互补充
数据查询语言
命令式和声明式
function getSharks() {
var sharks = [];
for (var i = 0; i < animals.length; i++) {
if (animals[i].family === 'Sharks') {
sharks.push(animals[i]);
}
}
return sharks;
}
SELECT * FROM animals WHERE family = 'Sharks';
声明式(declarative)语言 | 命令式(imperative)语言 | |
概念 | 描述控制逻辑而非执行流程 | 描述命令的执行过程,用一系列语句来不断改变状态 |
举例 | SQL,CSS,XSL | IMS,CODASYL,通用语言如 C,C++,JS |
抽象程度 | 高 | 低 |
解耦程度 | 与实现解耦。 | 与实现耦合较深。 |
解析执行 | 词法分析 → 语法分析 → 语义分析 | 词法分析 → 语法分析 → 语义分析 |
多核并行 | 声明式更具多核潜力,给了更多运行时优化空间 | 命令式由于指定了代码执行顺序,编译时优化空间较小。 |
相对声明式语言,命令式语言的优点?
- 当描述的目标变得复杂时,声明式表达能力不够。
- 实现命令式的语言往往不会和声明式那么泾渭分明,通过合理抽象,通过一些编程范式(函数式),可以让代码兼顾表达力和清晰性。
web中
css标签和源生js代码,不懂前端,略
MapReduce
goole提出的编程模型,用于分布式批量处理海量数据
MongoDB 的 MapReduce 模型
MongoDB 使用的 MapReduce 是一种介于
- 声明式:用户不必显式定义数据集的遍历方式、shuffle 过程等执行过程。
- 命令式:用户又需要定义针对单条数据的执行过程。
两者间的混合数据模型。
需求:统计每月观察到鲨类鱼的次数。
查询语句:
PostgresSQL
SELECT date_trunc('month', observation_timestamp) AS observation_month,
sum(num_animals) AS total_animals
FROM observations
WHERE family = 'Sharks' GROUP BY observation_month;
MongoDB
db.observations.mapReduce(
function map() {
// 2. 对所有符合条件 doc 执行 map
var year = this.observationTimestamp.getFullYear();
var month = this.observationTimestamp.getMonth() + 1;
emit(year + '-' + month, this.numAnimals); // 3. 输出一个 kv pair
},
function reduce(key, values) {
// 4. 按 key 聚集
return Array.sum(values); // 5. 相同 key 加和
},
{
query: { family: 'Sharks' }, // 1. 筛选
out: 'monthlySharkReport', // 6. reduce 结果集
}
);
上述语句在执行时,经历了:筛选 → 遍历并执行 map → 对输出按 key 聚集(shuffle)→ 对聚集的数据进行 reduce → 输出结果集。
MapReduce 一些特点:
- 要求 Map 和 Reduce 是纯函数。即无任何副作用,在任意地点、以任意次序执行任何多次,对相同的输入都能得到相同的输出。因此容易并发调度。
- 非常底层、但表达力强大的编程模型。可基于其实现 SQL 等高级查询语言,如 Hive。
但要注意:
- 不是所有的分布式 SQL 都基于 MapReduce 实现。
- 不是只有 MapReduce 才允许嵌入通用语言(如 js)模块。
- MapReduce 是有一定理解成本的,需要熟悉其执行逻辑才能让两个函数紧密配合。
图状数据模型
适用场景?大量的多对多关系
图数据模型的基本概念一般有三个:点,边和附着于点和边之上的属性。
常见的可以用图建模的场景:
例子 | 建模 | 应用 |
社交图谱 | 人是点,follow 关系是边 | 六度分隔,信息流推荐 |
互联网 | 网页是点,链接关系是边 | PageRank |
路网 | 交通枢纽是点,铁路/公路是边 | 路径规划,导航最短路径 |
洗钱 | 账户是点,转账关系是边 | 判断是否有环 |
知识图谱 | 概念时点,关联关系是边 | 启发式问答 |
- 同构(homogeneous)数据和异构数据 图中的点可以都具有相同类型,但是,也可以具有不同类型,并且更为强大。
对图的建模方式:
- 属性图(property graph):比较主流,如 Neo4j、Titan、InfiniteGraph
- 三元组(triple-store):看不下去,如 Datomic、AllegroGraph
图是一种很灵活的建模方式:
- 任何两点间都可以插入边,没有任何模式限制。
- 对于任何顶点都可以高效(思考:如何高效?)找到其入边和出边,从而进行图遍历。
- 使用多种标签来标记不同类型边(关系)。
相对于关系型数据来说,可以在同一个图中保存异构类型的数据和关系,给了图极大的表达能力!
这种表达能力,根据图中的例子,包括:
- 对同样的概念,可以用不同结构表示。如不同国家的行政划分。
- 对同样的概念,可以用不同粒度表示。比如 Lucy 的现居住地和诞生地。
- 可以很自然的进行演化。
将异构的数据容纳在一张图中,可以通过图遍历,轻松完成关系型数据库中需要多次 Join 的操作
图模型和网络模型
图模型是网络模型旧瓶装新酒吗?
不是,他们在很多重要的方面都不一样。
网上查找到一句话,觉得说得很好。网状模型数据库或多或少仍然采用了层次模型的思想,从父子关系的角度出发思考结点之间的关系。这就意味着在网状模型数据库中无法在结点之间随意建立关系,导致面向图数据的数据集工作起来很困难。
同时,网状模型数据库对于结点的属性需要提前定义好,而图数据库使用更加灵活的属性图模型,允许随意向结点和边添加属性
模型 | 图模型(Graph Model) | 网络模型(Network Model) |
连接方式 | 任意两个点之间都可以有边 | 指定了嵌套约束 |
记录查找 | 1. 使用全局 ID | 只能使用路径查询 |
有序性 | 点和边都是无序的 | 记录的孩子们是有序集合,在插入时需要考虑维持有序的开销 |
查询语言 | 既可命令式,也可以声明式 | 命令式的 |