Skip to content

NoSQL 数据建模 详解

文档、键值、宽列、图与向量数据库的建模模式 —— 何时嵌入、引用、反规范化或按时间分桶。

NoSQL 存储用固定 schema 和 join 换取横向扩展能力。每一类数据库都以自己的方式建模关系,因此决定结构的是访问模式,而不是范式。

参考表格 · 28 条目
28 of 28 rows
文档(Document)
嵌套的子文档存放在一个父文档内部,而不是拆成独立的表。需要一起读取的一对少关系。订单及其行项目;拥有三个地址的用户。
只存外键 id,不存数据。用第二次读取或 $lookup 来解析。大型、共享或独立更新的子实体。跨 collection 的多对多链接。
父文档内嵌一个短的子文档数组;整个集合刚好装下并作为一个文档加载。资料上的几个电话号码或偏好项。一次读取同时返回父与子。
子文档住在自己的 collection 里;每个子文档携带父 id,或父文档保留一个有上限的 id 数组。过大或过于独立、不适合内嵌的集合。按父 id 查询子文档。
子文档存储父 id;父文档永不列出子文档,因为集合没有上界。绑定到主机或用户的事件、日志、投票。按父 id 过滤子文档并分页。
不同实体类型的文档共用一个 collection,靠 type 字段区分。挂接在多种父对象上、且用同一种方式查询的评论或通知。
把少数极端实体从共享形态中拆出去,使其体积或流量不扭曲其余数据。粉丝列表或计数器会超出普通文档的名人账号。
每个修订版都是同 id、版本号递增的新文档;查询时取最新版。需要旧修订版仍可查询的审计轨迹和编辑历史。
派生值在写入时计算,并存放在它所汇总的数据旁边。否则每次读取都要跑聚合的总计、评分和 Top-N。
键值与宽列
DynamoDB 设计术语:用重载的分区键和排序键把多种实体类型收进一张表。一次查询取回异构 item,无需 join。
一个键属性服务多种实体类型,用 USER# 或 ORDER# 之类前缀标记。单表设计。把相关 item 聚在一个分区下,一次查询全部取回。
分区键把行分散到各节点;排序键在每个分区内排序。Cassandra 和 DynamoDB。范围扫描、有序日志、层级复合键。
把多个属性叠进一个带分隔符的排序键 —— country#region#city —— 前缀匹配即可沿层级下钻。层级结构和多维度过滤。一个有序范围查询替代多次索引查找。
把事件归入固定窗口 —— 小时、天、月 —— 放在专用分区或表下。指标、日志与物联网遥测。限制分区大小,并让冷数据随时间淘汰。
在读多字段上跨记录复制,查询就不必再追第二次查找。可容忍最终一致性、存储便宜的读多写少负载。
建立在只有部分 item 才有的属性上的二级索引;没有该属性的 item 永远不会进入索引。备用访问路径。无需全表扫描即可找到稀疏子集 —— 未关闭的订单、未发货的 item。
先写下确切的查询;表结构由查询推导而来,而非来自实体或范式。Cassandra 和 DynamoDB 的 schema 设计。每个查询族一张表;数据模型照抄问题本身。
因为没有键匹配问题,查询不得不并行命中大量分区或节点。这是要设计掉的反面信号,不是目标。重排表的键,让常见查询落到单个分区。
副本在写入时推送到每个读者的视图,或在读取时收集合并。信息流和时间线。小受众用写时扇出,庞大受众用读时扇出。
预先构建的查询结果存成自己的表,并随基表变化保持同步。单一写入路径之上的多种读取形态。回答基表无法回答的查询。
图(Graph)
每条边都是从源节点指向目标节点的一行或元组。图和属性存储。朋友的朋友查询、路由、推荐。
每个关系是独立 collection 中的一个文档,携带 from 与 to 的 id 及链接属性。文档存储里的图。属性挂在链接本身上 —— 评分、角色、时间戳。
每个节点存储深度优先遍历得出的左右计数器;子树落在父节点的区间内。读多写少的树。一条 BETWEEN 查询返回整棵子树;插入要重排遍历编号。
一张专用表记录每一对祖先-后代,包括深度为零的节点自身。任意深度的整棵分支查询。简单的 join,代价是存储和维护。
属性图把键值属性附加到节点和边上;RDF 把一切表述为主-谓-宾三元组。丰富遍历用属性图(Neo4j);关联开放数据和本体交换用 RDF。
向量(Vector)
高维浮点数组与元数据并存。ANN 索引查找最近邻。语义搜索、RAG 检索、推荐,以及非结构化内容上的相似度。
源文档先切分 —— 按大小、重叠或结构 —— 再对每一块单独嵌入。RAG 检索质量。块大小在精确率与召回率之间取舍;重叠保护被切断的句子。
稠密向量相似度与关键词匹配并行运行;两个排序表融合成一个结果。同时需要精确词项与语义的查询,并可叠加元数据过滤。