24

文档数据库中的每个集合是否应该有一个实体?

考虑我在下图中有外键关系: 在此处输入图像描述

我应该为员工创建两个集合,另一个为公司创建。或者我应该将它们存储到一个集合中?

我在这里读到,在存储过程触发器的 documentdb 范围内,触发器等都在一个集合中。因此,通过将不同的实体拆分为单独的集合,我失去了开箱即用的功能。

因此,将这两个类作为单个实体转储不是更好,如下所示:

{
  "Id": 1001,
  "Industry": "Software",
  "Employees": [
    {
      "Id": 10011,
      "Name": "John Doe",
      "CompanyId": 1001
    },
    {
      "Id": 10012,
      "Name": "Jane Doe",
      "CompanyId": 1001
    }
  ]
}

在 DocumentDB 中实现相关实体的标准做法是什么?

4

3 回答 3

62

每个集合存储多个实体类型通常是好的。是否将实体类型存储到单个文档中需要更多考虑。

正如大卫所提到的 - 如何对数据进行建模有点主观。

在集合中存储多个实体类型

首先......让我们谈谈在一个集合中存储多个实体。DocumentDB 集合不是表。集合不强制执行模式;换句话说,您可以在同一个集合中存储具有不同模式的不同类型的文档。您只需向文档添加类型属性即可跟踪不同类型的实体。

您应该将集合视为执行查询和事务的分区和边界单元。因此,在同一个集合中存储不同实体类型的一个巨大好处是您可以通过 sprocs 直接获得事务支持。

在文档中存储多个实体类型

是否在单个文档中存储多个实体类型需要更多考虑。这通常被称为去规范化(通过在单个文档中嵌入数据来捕获数据之间的关系)和规范化(通过创建到其他文档的弱链接来捕获数据之间的关系)您的数据。

通常,反规范化可提供更好的读取性能。

应用程序可能需要发出更少的查询和更新来完成常见操作。

通常,在以下情况下使用非规范化数据模型:

  • 实体之间有“<em>contains”关系
  • 实体之间有一对多的关系
  • 非规范化数据不经常更改
  • 非规范化数据不会无限制地增长
  • 非规范化数据是文档中数据的组成部分

非规范化数据模型的示例:

{
  "Id": 1001,
  "Type": "Company",
  "Industry": "Software",
  "Employees": [
    {
      "Id": 10011,
      "Type": "Employee",
      "Name": "John Doe"
    },
    {
      "Id": 10012,
      "Type": "Employee",
      "Name": "Jane Doe"
    }
  ]
}

通常规范化提供更好的写入性能。

提供比反规范化更大的灵活性

客户端应用程序必须发出后续查询来解析引用。换句话说,规范化的数据模型可能需要到服务器的往返次数更多。

通常,使用标准化数据模型:

  • 当反规范化会导致数据重复但不会提供足够的读取性能优势来超过重复的影响时。
  • 表示一对多关系
  • 表示多对多关系。
  • 相关数据变化频繁

标准化数据模型示例:

{
  "Id": 1001,
  "Type": "Company",
  "Industry": "Software"
}

{
  "Id": 10011,
  "Type": "Employee",
  "Name": "John Doe",
  "CompanyId": 1001
}

{
  "Id": 10012,
  "Type": "Employee",
  "Name": "Jane Doe",
  "CompanyId": 1001
}

混合方法

在规范化和反规范化之间进行选择不一定是非黑即白的选择。我经常发现一个成功的设计模式是一种混合方法,在这种方法中,您可以选择规范化对象字段的部分集合,并对其他字段进行反规范化。

换句话说,您可以选择去规范化频繁读取的稳定(或不可变)属性以减少后续查询的需要,同时规范化频繁写入/变异的字段以减少扇出写入的需要。

混合方法的示例:

// Author documents:
[{
  "id": 1,
  "firstName": "Thomas",
  "lastName": "Andersen",
  "countOfBooks": 3,
  "books": [1, 2, 3],
  "images": [{
    "thumbnail": "http://....png"
  }, {
    "profile": "http://....png"
  }, {
    "large": "http://....png"
  }]
}, {
  "id": 2,
  "firstName": "William",
  "lastName": "Wakefield",
  "countOfBooks": 1,
  "books": [1, 4, 5],
  "images": [{
    "thumbnail": "http://....png"
  }]
}]

// Book documents:
[{
  "id": 1,
  "name": "DocumentDB 101",
  "authors": [{
    "id": 1,
    "name": "Thomas Andersen",
    "thumbnailUrl": "http://....png"
  }, {
    "id": 2,
    "name": "William Wakefield",
    "thumbnailUrl": "http://....png"
  }]
}, {
  "id": 2,
  "name": "DocumentDB for RDBMS Users",
  "authors": [{
    "id": 1,
    "name": "Thomas Andersen",
    "thumbnailUrl": "http://....png"
  }, ]
}]
于 2014-12-14T03:25:45.660 回答
5

你的问题有点主观,因为你要求实体设计,为此,没有一个正确的答案。

但是:从更客观的角度来看:没有什么可以阻止您在集合中拥有多个实体类型Company(例如,在您的情况下,文档类型和Employee文档类型)。

您需要为自己添加某种类型的提示(可能是type属性),以在运行查询时帮助区分两者。但是,通过将这两种类型都放在同一个集合中,您现在就有了一个可以在其中工作的集合范围。关于type属性:由于 DocumentDB 默认索引所有属性,type因此属性很容易集成到您的查询中。

编辑删除了关于每个容量单位 3 个集合的部分,因为当 DocumentDB 从预览转移到生产时,该安排被删除。

于 2014-12-13T15:04:59.940 回答
1

在过去 5 年中,Cosmos DB 发生了许多变化,影响数据结构设计的最重大变化之一是可以创建许多容器并在所有容器之间共享 RU。

在同一个容器中组合多个实体类型仍然没问题(集合的新名称)。但是,在 2020 年,也可以将每个实体类型放在单独的容器中。

当然,这取决于您的应用程序的需求,一个非常重要的考虑因素是您打算如何阅读此信息。但是,您可以考虑以下一般数据结构和方法:

  1. 将每个实体保存在自己的容器中
  2. 包含带有实体名称的属性
  3. 以某种方式选择分区键,这样一个分区中的数据永远不会超过 10 GB
  4. 确定由于大量调用而需要最佳性能的读取
  5. 这是踢球者:将数据复制到针对预期读取优化的容器

对于您想要优化的那些读取,将数据复制到专用于该目的的新容器中,并确保分区键与您查询的主要参数相匹配。您可以将许多不同的实体放在同一个容器中。

你会发现这样的读取操作效率要高很多倍。

Cosmos DB 性能很大程度上取决于数据量,如果您确保通过分区键可以轻松访问您的文档,那么将大量数据放在单个文档中而不是保留它们不会获得任何显着的性能提升在单独的文件中。

例子

你有两个容器:

  • 订单
  • 订单详细信息

订单按productId分区,订单详细信息按orderId分区。但是对于一个显示单个用户的订单历史的新功能来说,它花费了太多的 RU,不仅要通过userId属性获取订单,而且特别是要对每个订单进行后续调用以获取已下订单的详细信息在单独的分区中。

相反,幸运的是,这两个文档都包含一个userId属性。您所做的是创建一个新容器,可能称为orders-by-user并将userId属性配置为分区键。然后将订单订单详细信息中的所有文档复制到此容器。

您现在可以通过userId对该容器进行非常高效的读取。

您可以使用基于更改源的数据工厂、Azure 函数进行复制,很快就会有一个用于此目的的内置功能(查看评论): https ://stackoverflow.com/a/64355508/392362

于 2020-10-16T07:10:42.733 回答