读书笔记 · 约 13,492 字 · 预计推送 2 部分

《Designing Data-Intensive Applications》第一部分·数据模型与查询语言(主题精读) 未开始

目录

题记与说明


0. TL;DR

这一章回答一个看似简单的问题:该用什么形状来装你的数据? Kleppmann 的答案不是一个模型名,而是一套判据:先看数据里主导的关系形态——一对多的树状结构(简历、订单、用户资料)文档模型最自然;简单的多对一/多对多关系关系模型足够;一旦数据"任何东西都可能和任何东西相关"(社交图谱、知识图谱、组织架构),就该上图模型。历史上这场竞赛已经跑过一轮:层次模型(树)→ 网状模型(指针互联)→ 关系模型靠查询优化器胜出,而 NoSQL 的文档模型某种意义上是层次模型的螺旋回归。另一条主线 equally重要:声明式查询语言(SQL/Cypher/SPARQL/Datalog)让"查什么"与"怎么查"解耦,把优化和并行的自由还给了数据库——同一个图查询,Cypher 4 行,SQL 递归 CTE 要 31 行。无模式(schemaless)是个误导性说法,准确的说法是读时模式 vs 写时模式,类比动态类型 vs 静态类型。两个阵营正在融合:关系数据库加 JSON,文档数据库加 join 和二级索引——"关系模型与文档模型的混合是一种强大的组合"。


1. 核心问题

本章要解决的核心问题是:数据模型不是存储细节,而是"解题思路"的塑造者——那么在关系、文档、图等模型之间,依据什么来选择? 拆开是四个子问题:

  1. 表达力问题:同一份真实世界数据(人员、组织、商品、行为),用关系表、JSON 文档还是图的顶点和边来建模?哪些操作在一种模型里容易、在另一种里别扭?
  2. 阻抗不匹配问题:面向对象代码里的对象与数据库的表/行/列之间的转换层(ORM 能否彻底解决)?
  3. schema 问题:数据库该不该强制数据结构?写时强制(schema-on-write)与读取时解释(schema-on-read)各自的代价与适用场景?
  4. 查询语言问题:为什么声明式(declarative)查询语言是数据库给应用层的核心红利?MapReduce 这类"代码即查询"的方式处在什么位置?高度互联的数据用什么语言查才不别扭?
章首题词与开篇(第 1 版英文原文,O'Reilly 官方预览页):

> "The limits of my language mean the limits of my world." — Ludwig Wittgenstein, *Tractatus Logico-Philosophicus* (1922)

> "Data models are perhaps the most important part of developing software, because they have such a profound effect: not only on how the software is written, but also on how we think about the problem that we are solving."

2. 主要主张

2.1 分层数据模型:每一层的唯一关键问题是"如何用低一层表示"

第 1 版开篇给出四层叠放(O'Reilly 预览页原文,编号照录):

  1. 应用开发者把现实世界(人、组织、商品、行为、资金流、传感器……)建模为对象/数据结构 + 操作它们的 API(通常应用特有);
  2. 存储时用通用数据模型表达:JSON/XML 文档、关系表、或图模型;
  3. 数据库工程师决定如何在内存/磁盘/网络的字节上表示这些文档、关系或图;
  4. 更低层,硬件工程师用电流、光脉冲、磁场表示字节。
中文译本(第 2 版):"每个层都通过提供一个明确的数据模型来隐藏更低层次中的复杂性。这些抽象允许不同的人群有效地协作,例如数据库厂商的工程师和使用数据库的应用程序开发人员。"

关键推论:每一层模型都内嵌了"它会被怎么用"的假设——某些操作快、某些操作别扭。选模型就是在选哪些操作是"一等公民"。

2.2 历史循环:文档模型是层次模型的回归,关系模型赢在查询优化器

2.3 对象-关系阻抗不匹配:ORM 是止痛药,不是解药

中文译文(第 2 版):"数据若存储在关系表中,就需要一个笨拙的转换层,在应用代码中的对象与数据库的表、行、列模型之间来回转换。两种模型之间的这种脱节,有时称为*阻抗不匹配*(impedance mismatch)。"(术语借自电子学:阻抗不匹配导致信号反射。)

第 2 版把 ORM 的账算得很细(第 1 版只有一段,这是重要增量):ORM 本身复杂且无法彻底掩盖两种模型差异;只覆盖 OLTP 场景;自动生成的模式未必友好;且容易引入 N+1 查询问题——取 N 条评论后再逐条查作者,本可在数据库里一次 join 完成。JSON 文档之所以让人觉得"更自然",正是因为它贴着应用对象结构。

2.4 关系形态定模型:一对多 → 文档;多对一/多对多 → 引用与 join

贯穿案例是 LinkedIn 简历(第 1 版图 2-1;第 2 版图 3-1,示例数据甚至换成了 Obama 的简历):

2.5 无模式是误导:读时模式 vs 写时模式

中文译文(第 2 版):"文档数据库有时称为*无模式*(schemaless),但这具有误导性,因为读取数据的代码通常假定某种结构——即存在隐式模式,只是不由数据库强制执行。一个更准确的术语是*读时模式*(schema-on-read,数据结构是隐含的,只有读取时才会解释),与之相对的是*写时模式*(schema-on-write,关系数据库的传统做法:模式是明确的,数据库确保写入的所有数据都符合模式)。"

2.6 局部性:文档模型最大的性能论据,且有边界

2.7 融合:两个阵营正在互相学习

中文译文(第 2 版):"这种融合对应用程序开发人员来说是件好事,因为关系模型和文档模型能够在同一个数据库中结合使用时,最能发挥各自所长。……关系模型与文档模型的混合是一种强大的组合。"

2.8 声明式 vs 命令式:数据库给应用层的最大红利

2.9 图模型:当"任何东西都可能和任何东西相关"时

中文译文(第 2 版总结):"*文档模型*主要关注自包含的 JSON 数据文档,而且文档之间的关系非常稀少。*图数据模型*用于相反的场景:任意事物都可能与其他一切事物相关,查询可能需要跨越多跳才能找到感兴趣的数据。"

2.10 第 2 版的两个大增量(第 1 版没有):事件溯源/CQRS 与数据框

3. 方法

书本身的论证方法(值得单独学):

  1. 分层抽象框架开题:先把"数据模型"定义为层与层之间的表示契约,之后所有比较都挂在这个框架上;
  2. 历史叙事:层次/网状 → 关系 → NoSQL → 融合,用"历史是否在重复"来检验每个新主张("Are Document Databases Repeating History?" 小节标题本身就是论点);
  3. 单一贯穿案例反复变奏:LinkedIn/Obama 简历跑完关系版、JSON 版、多对多版、规范化版;鲨鱼观察跑完 SQL 版、MongoDB 版、MapReduce 版;Lucy/Alain 图跑完 Cypher、SQL、SPARQL、Datalog 四种语言——同题多解,差异自己浮现;
  4. 代码即证据:4 行 vs 31 行的对比不靠形容词,靠把两段代码完整摆出来;
  5. 刻意不下绝对结论:每个权衡都给出甜点区与失效条件(局部性的边界、读时模式的适用条件)。

我本次的精读方法:先 WebSearch 定位来源 → 精读 O'Reilly 官方预览页拿到第 1 版英文原文开头、章节题词与完整小节目录(作为结构基准)→ 精读 Vonng 第 2 版中文全译拿到全文级细节与中文引述 → 用 timilearning、hashnode ×2、dev.to 三份第 1 版笔记交叉核对(尤其 MapReduce、NoSQL 四动因、历史模型等第 2 版已改写的部分)→ 明确标注两版差异,避免把第 2 版内容错安到第 1 版章名下。

4. 数据与证据

本章证据是例证性/代码对比性的,不是实验测量,具体包括:

证据内容来源
章节结构第 1 版三大节:Relational Model Versus Document Model(含 The Birth of NoSQL / The Object-Relational Mismatch / Many-to-One and Many-to-Many / Are Document Databases Repeating History? / Relational Versus Document Databases Today)、Query Languages for Data(含 Declarative Queries on the Web / MapReduce Querying)、Graph-Like Data Models(Property Graphs / Cypher / Graph Queries in SQL / Triple-Stores and SPARQL / The Foundation: Datalog)O'Reilly 官方小节目录
原文开篇"Data models are perhaps the most important part of developing software…" + 四层模型清单O'Reilly 预览页正文
简历案例关系模式(users + positions/education/contact_info 外键)vs JSON 文档;region_id 用 ID 还是文本的规范化论证;多对多需 associative tableVonng 译文图 3-1/3-3/示例 3-1/3-2;hashnode part 1
鲨鱼查询PostgreSQL date_trunc('month',…)+GROUP BY vs MongoDB $match+$group 聚合管道Vonng 译文 §文档的查询语言
代码行数对比同一"美国→欧洲移民"查询:Cypher 4 行 vs SQL WITH RECURSIVE 31 行(含环处理、遍历顺序等更多细节"这还只是开始")Vonng 译文示例 3-5/3-6 及原文评述
ORM 批判清单复杂度高、只覆盖 OLTP、生成模式不友好、N+1 查询问题;以及对应的优势清单Vonng 译文 §ORM
在线迁移工具pt-online-schema-change、gh-ost、pg-osc、pgroll 等 4 篇参考文献Vonng 译文脚注 21–24
局部性反例组Spanner 表交错、Oracle 多表索引集群表、Bigtable/HBase 列族Vonng 译文脚注 25–27
图模型实现组Neo4j/Memgraph/KùzuDB(属性图);Datomic/AllegroGraph/Blazegraph(三元组);Amazon Neptune 两者都支持Vonng 译文 §图数据模型
第 2 版产业佐证MongoDB 弃用 mapReduce 改聚合管道;GQL 进 SQL 标准计划;Stonebraker & Pavlo "What Goes Around Comes Around… And Around…"(SIGMOD 2024)Vonng 译文脚注 31/42

证据是否充分:对"模型选择取决于关系形态"这一核心论点,案例充分且相互独立(简历/鲨鱼/社交图三案例覆盖三种形态);但所有性能判断(局部性更快、join 更慢)均为定性论证,无 benchmark 数据——这类结论只能当"经验法则"用。

5. 结论

  1. 没有普适最优的数据模型。选择判据是数据中主导的关系形态:一对多树状 → 文档模型;简单多对一/多对多 → 关系模型;高度互联、需要变长路径遍历 → 图模型。(第 1 版经验法则,经 hashnode part 1 复述:高度互联数据上,"document is awkward, relational is acceptable, graph is most natural"。)
  2. 无模式 ≠ 无结构:结构一定存在,区别只在于强制在写入时(显式)还是假定在读取时(隐式)——映射到动态/静态类型之争,答案取决于数据是否异构、由谁控制。
  3. 声明式查询语言是数据库百年竞争沉淀出的核心红利:它把优化自由还给引擎、把并行自由还给硬件;图查询语言的多样性(Cypher/SPARQL/Datalog/GQL)说明这个红利在关系模型之外同样成立。
  4. 两个阵营在融合而非替代:关系库 + JSON 文档列、文档库 + join/二级索引,"混合是最强组合";Codd 的 nonsimple domain 说明这一融合甚至是"回到 1970"。
  5. 第 2 版的增补(事件溯源/CQRS、数据框)把本章的框架从"选一种模型"推进为"写入优化表示 + 派生读取优化表示"的模型组合论——这正是第 1 版第 10–12 章"Derived Data"思想前置到数据模型层面的体现。

6. 局限

作者承认/明示的:

我推断的:

7. 对我的启发

结合我已有的笔记体系(PostgreSQL JSONB/MVCC、SQLite、Redis、混合检索、事件驱动消息队列):

  1. 选型流程模板:拿到建模需求先画"关系形态图"——数一数一对多、多对一、多对多、递归互联各占多少。主导形态直接映射模型选择;这条比"新项目用 MongoDB 还是 PostgreSQL"式的站队问题有用得多。
  2. PostgreSQL 是"融合论"的最佳现实验证:关系骨架 + JSONB 文档叶 + 两者都可建索引,等于把 schema-on-write 与 schema-on-read 放进同一个库。我的个人项目默认这条路,把"要不要文档库"变成"哪部分字段值得提升为列"。
  3. 递归关系警惕 SQL 递归 CTE 的 31 行税:组织架构、评论树、BOM 这类变长路径查询,若频率高,评估 WITH RECURSIVE 的可维护性成本 vs 图扩展(如 Apache AGE)或专用图库;低频查询则 31 行也能接受。
  4. ORM 的 N+1 是文档/关系混用时的经典暗坑:凡是"列表页 + 每行再查关联对象"的代码都值得 grep 一遍;手写 join 或显式 eager loading。
  5. 反规范化 = 引入衍生数据:一旦决定冗余存储,就必须回答"谁负责保持一致"(应用双写?物化视图?CDC/事件流?)——这一条与第 2 版新增的事件溯源/CQRS 一节、以及 Twitter 时间线案例形成闭环:反规范化的一致性成本正是流处理系统存在的理由。
  6. 声明式思维可以迁移:设计 Agent 工具 API 或 Prompt 时,同样遵循"说 what,把 how 留给执行引擎"——把排序、过滤、聚合的优化权留给被调用方,接口才不会随实现演进而腐化。

附:本次精读来源链接

  1. O'Reilly 官方第 1 版第 2 章预览与目录:<https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/ch02.html>
  2. Vonng《DDIA》第 2 版中文译·数据模型与查询语言:<https://ddia.vonng.com/ch3>(仓库:<https://github.com/Vonng/ddia>)
  3. Timilearning 读书笔记:<https://timilearning.com/posts/ddia/part-one/chapter-2>
  4. Hashnode 深读 Part 1:<https://sayedalesawy.hashnode.dev/designing-data-intensive-applications-ch2-data-models-and-query-languages-or-part-1>;Part 2:<https://sayedalesawy.hashnode.dev/designing-data-intensive-applications-ch2-data-models-and-query-languages-or-part-2>
  5. Dev.to 章节总结:<https://dev.to/faizanfirdousi/understanding-database-models-ddia-chapter-2-36jj>
  6. Dan Lebrero 要点笔记:<https://danlebrero.com/2021/09/01/designing-data-intensive-applications-summary>

*说明:英文引述取自 O'Reilly 官方页面可见的原文片段;中文引述取自 Vonng 第 2 版中文译文(CC 授权译本);标注"第 1 版"而来源为笔记类站点的内容,均经过至少两个来源交叉核对;未能核对的细节已在文中注明来源层次。*

← 资料库今日播报 →