Kyle's Notebook

《DDIA》阅读笔记(二):数据模型与查询语言

Word count: 2.3kReading time: 7 min
2020/12/04

《DDIA》阅读笔记(二):数据模型与查询语言

本章结构如下:

  • 数据模型与查询语言
    • 数据模型
      • 关系模型(严格关联)
        • 数据访问
        • 局限性
        • 多对一关系
        • 多对多关系
      • 非关系模型
        • 文档模型(弱关联)
          • 灵活性
          • 局限性
          • 数据局部性
        • 图模型(复杂关联)
          • 属性图模型
          • 三元存储模型
          • 图处理框架
    • 数据查询语言
      • 声明式语言
      • MapReduce
      • 图查询语言
        • 声明式
          • Cypher
          • SPARQL
          • Datalog
          • SQL
        • 命令式
          • Gremlin

关于数据模型:

  • 复杂的应用程序可能会有更多的中间层,每层通过提供简洁的数据模型隐藏下层复杂性。抽象机制使得不同的人群可高效协作。

  • 有许多不同类型的数据模型,都分别有其最佳实践。即使不关心内部机制,精通一种数据模型需要很大功夫。由于对其上的软件应用有巨大影响,需要慎重选择合适的数据模型。

数据模型

关系模型:严格关联

数据被组织成关系(Repations),在 SQL 中为表(Table),每个关系是元组(Tuples)或无序集合(行),其实现为关系数据库管理系统(RDBMS)和 SQL。

数据访问

定义所有数据:

  • 固定格式:关系(表)是元组(行)的集合,没有复杂的嵌套结构和访问路径,可读取表中任何行,支持任意条件查询。

  • 可指定某些列作为键并匹配列来读取特定行。

  • 可在任何表中插入新行,不必担心与其他表之间的外键关系。

  • 优势在于联结操作、多对一和多对多关系更简洁的表达上。

  • 查询优化器:只需构建一次,所有应用都可以从中受益。由查询优化器决定以何种顺序执行查询、使用的索引等。

局限性

  • 数据存储在关系表中,应用层代码中的对象与表、行和列的数据库模型之间需要笨拙的转换层。模型之间脱离被称为 阻抗失谐。使用 ORM 可减少转换层的样板代码,但不能完全隐藏两个模型之间的差异。

  • 遵顼严格的 写时模式,在数据量大时模式更改、数据更新速度缓慢,甚至可能需要停机。可借鉴文档数据库:采用 读时填充,设置字段为 NULL、在读取时填充值。

多对一关系

将诸如地址、行业等定义为 id(设计为字典表),在填写时供用户选择而非输入:

  • 保持样式和输入值一致

  • 避免歧义(广州和广州市)

  • 易于更新

  • 本地化支持

  • 搜索支持

多对一 关系(多人生活于某个地区、从事某一行业)使用关系模型更合适:

  • 相关项由唯一标识符引用(外键,在文档模型中为文档引用),可在查询时通过联结或相关后续查询来解析。

  • 如数据库不支持联结,必须在应用代码中对数据库进行多次查询后再模拟联结(从持久层转移到应用层)。

  • 随着应用功能迭代,即使初始版本采用无联结的文档模型,数据也可能变得更互联一体化。

多对多关系

更建议使用图模型。

非关系模型

主要指 NoSQL,其提供数据模型、复制和分区等手段改进传统关系模型。

  • 更好的扩展性需求。

  • 丰富的免费和开源软件。

  • 满足特定需求的查询操作。

  • 更少限制、更具动态和表达力的数据模型(不对数据强加模式)。

非关系模型 解决关系模型的问题 代表实现
键值数据库 无法存储数据结构。 Redis
文档数据库 强 schema 约束,不够灵活。 MongoDB
列式数据库 大数据场景下部分访问 I/O 性能差。 HBase
全文搜索引擎 全文搜索性能差。 Elasticsearch

有时也结合关系模型,称为 混合持久化

文档模型:弱关联

模式灵活性, 由于局部性带来较好性能,更接近应用程序使用的数据结构。

尤其适合处理自包含文档,且文档与其他文档之间关联很少。

灵活性

实现层次模型:在父记录中保存嵌套记录,而非存储在单独的表中。

  • 读时模式:在读数据的代码中采用某种隐形模式(而非数据库强制执行,类似运行时类型检查),适用于大量异构或动态结构的数据存储。解决 写时模式 在更改 schema 时面临的速度慢和停机的问题(尤其在数据量大时)。

  • 非关联树状结构容易表达不需要联结或只需要弱连结的 一对多 关系(一名用户身兼多个职位、拥有多段工作经历)等。在读取时一次性加载整棵树,无需像关系模型般把结构分解为多个笨重而复杂的表。

局限性
  • 不能直接引用嵌套项,难以处理嵌套太深的结构。

  • 在表示 多对一多对多 关系时,与关系模型一样,相关项由外键或文档引用引用。可在查询时通过联结操作或相关后续查询来解析(文档数据库并未遵循 CODASYL 标准)。

  • 处理多对多关系时可通过反规范化减少联结需求,应用代码需做额外工作保持数据一致性,实现更复杂且比基于数据库联结更慢。

数据局部性

文档通常存储为 JSON、 XML 或其二进制变体(如 MongoDB 的 BSON)编码的连续字符串:

  • 存储局部性具有性能优势(关系模型数据划分在多个表,需要多次基于索引查找、更多的磁盘 I/O)。

  • 仅适用 同时访问文档大部分内容 的场景,如只访问其中一小部分则比较浪费。

  • 更新时重写整个文档,只有修改量不改变源文档大小时,原地覆盖更新才更有效。建议文档尽量小且避免写入时扩增文档大小。

图模型:复杂关联

图模型目标用例是所有数据都可能会互相关联,适用于数据关联比较复杂的场景(比如 Page Rank)。

  • 由顶点和边组成。每个顶点可连接到其他任何顶点(而不是由模式限制关联),给定顶点可高效地得到所有入边和出边,从而遍历图。

  • 除了同构数据,对于单个数据存储区,还提供了保存不同类型对象的一致性方式:对不同类型的关系使用不同标签,可在单个图中存储多种类型的信息,同时仍然保持整洁的数据模型。

相关模型和处理框架:

  • 属性图模型:比如 Neo4j 、 Titan 和 InfiniteGraph。

  • 三元存储模型:比如 Datomic、 AllegroGraph。

  • 图处理框架:比如 Pregel。

查询语言

声明式语言

命令式语言:如数据处理代码,可推理整个过程:逐行遍历代码、 评估相关条件、更新对应的变量,并决定是否再循环一遍。缺点是由于指定了特定的执行顺序,难以并行化。

声明式语言:如 SQL 与关系代数,只需指定所需的数据模式, 结果需满足的条件和转换数据的方式,不需指明如何实现目标。其隐藏了数据库引擎的细节,可在不改变查询语旬的情况下提高性能,通常适合于并行执行。

MapReduce

用于在许多机器上批量处理海量数据的编程模型。

  • 介于声明式和命令式查询 API 之间:查询逻辑用代码片段表示,代码片段可被框架重复调用。

  • map 和 reduce 函数必须为纯函数,只能接收固定输入,不能执行额外的数据查询、不能有副作用。因此可在任何位置、 以任意顺序运行函数,在失败时重新运行。

  • 某些 SQL 数据库可扩展使用 JavaScript 函数,但存在可用性问题:编写两个密切协调的函数通常比编写单个查询更难。

  • 聚合管道:声明式查询语言的支持,使用基于 JSON 的语法,在表达能力上相当于 SQL 子集。

图查询语言

声明式:

  • Cypher:用于属性图的声明式查询语言,最早为 Neo4j 图形数据库而创建。

  • SPARQL:采用 RDF 数据模型的三元存储查询语言。

  • Datalog

  • SQL:在关系数据库中,通常会预先知道查询中需要哪些 join 操作。而对于图查询,在找到要查找的顶点之前可能需要遍历数量未知的边,join 操作数量并不是预先确定的,使用 SQL 会非常繁琐。

命令式:Gremlin

CATALOG
  1. 1. 《DDIA》阅读笔记(二):数据模型与查询语言
    1. 1.1. 数据模型
      1. 1.1.1. 关系模型:严格关联
        1. 1.1.1.1. 数据访问
        2. 1.1.1.2. 局限性
        3. 1.1.1.3. 多对一关系
        4. 1.1.1.4. 多对多关系
      2. 1.1.2. 非关系模型
        1. 1.1.2.1. 文档模型:弱关联
          1. 1.1.2.1.1. 灵活性
          2. 1.1.2.1.2. 局限性
          3. 1.1.2.1.3. 数据局部性
        2. 1.1.2.2. 图模型:复杂关联
    2. 1.2. 查询语言
      1. 1.2.1. 声明式语言
      2. 1.2.2. MapReduce
      3. 1.2.3. 图查询语言