【问题标题】:Are there any benefits of using subcollections in firestore?在 Firestore 中使用子集合有什么好处吗?
【发布时间】:2019-06-13 10:24:27
【问题描述】:

我的应用程序的用户集合中的每个文档都有一个子集合。此子集合存储与用户相关的文档,但也可以将它们保存到主集合中,每个文档都有一个关联的 userId。

我选择了这种结构,因为它在当时似乎是最明显的,但我可以想象,如果我需要进行数据库维护,它会让事情变得更加困难。例如。如果我想清理这些文档,我必须先查询每个用户,然后再查询每个用户的文档,而如果我有一个主集合,我可以只查询所有文档。

这让我质疑子集合的意义何在,如果您可以将这些文档与 ID 相关联。它只是为了在您的文档接近 1MB 限制时可以扩展吗?

【问题讨论】:

  • 将这些文档保存在子集合(而不是主集合)下的一个可能缺点是您无法跨多个子集合进行查询。
  • 对,我就是这么说的。我不考虑转向子系列,我正在考虑远离它们。我想知道是否真的有充分的理由首先使用子集合

标签: firebase google-cloud-firestore


【解决方案1】:

编辑: 2021 年 10 月 29 日:

要清楚文档中存在的以下句子:

如果您不根据具有顺序值的字段进行查询。

时间戳只能被认为是连续的。但是,它仍然可以被认为是连续的。相同的规则适用于按字母顺序排列的(Customer1、Customer2、Customer3、...)或几乎一切可以被视为可预测生成的值。

Firestore 索引中的此类顺序数据,最有可能写入存储介质上的物理邻近位置,因此存在这种限制。

话虽如此,请注意 Firestore 使用一种机制将文档映射到相应的位置。这意味着如果这些值不是随机分布的,则写入操作将不会正确分布在这些位置上。这就是存在这种限制的原因。

另请注意,您可以在特定时间内写入此类位置的数据量存在物理限制。可预测的键/值很可能最终会出现在同一个位置,这实际上很糟糕。所以有更多的变化来达到这个限制。


编辑: 2021 年 7 月 16 日:

由于这个答案听起来有点陈旧,我将尝试添加一些使用随着时间的推移而发现的子集合的优势:

  1. 子集合将始终为您提供更结构化的数据库架构,因为您始终可以引用与特定文档相关的子集合。因此,您只能嵌套与特定文档相关的数据。
  2. 如前所述,maximum depth of a subcollection is 100。所以这里的一个重要特性是,Firestore 查询在 1 级时与在 100 级时一样快。所以不应该担心深度。此功能已经过测试。
  3. 默认情况下对子集合中的查询进行索引,就像顶级集合的情况一样。
  4. 就速度而言,Query是顶级集合、子集合还是集合组都无所谓,速度总是一样的,只要Query返回相同数量的文档.发生这种情况是因为查询性能取决于您请求的文档数量,而取决于您搜索的文档数量。因此查询子集合与查询顶级集合的效果相同,完全没有缺点。
  5. 在子集合中存储文档时,请注意无需将文档 ID 存储为字段,因为默认情况下它是参考的一部分。这意味着您可以在子集合中存在的文档中存储更少的数据。更重要的是,如果您将相同的数据保存在顶级集合中,并且您需要创建一个包含两个 whereEqualTo() 调用 + 一个 orderBy() 调用的查询,然后是一个 index would be required
  6. 在安全性方面,子集合允许继承安全规则,这很有用,因为我们可以编写越来越少的代码来保护数据库。

暂时就这样,如果我发现其他好处,我会更新答案。


让我们举个例子。假设我们有一个用于测验应用程序的数据库模式,如下所示:

Firestore-root
    |
    --- questions (collections)
          |
          --- questionId (document)
                 |
                 --- questionId: "LongQuestionIdOne"
                 |
                 --- title: "Question Title"
                 |
                 --- tags (collections)
                      |
                      --- tagIdOne (document)
                      |     |
                      |     --- tagId: "yR8iLzdBdylFkSzg1k4K"
                      |     |
                      |     --- tagName: "History"
                      |     |
                      |     --- //Other tag properties
                      |
                      --- tagIdTwo (document)
                            |
                            --- tagId: "tUjKPoq2dylFkSzg9cFg"
                            |
                            --- tagName: "Geography"
                            |
                            --- //Other tag properties

其中tagsquestionId 对象中的子集合。现在让我们创建 tags 集合作为顶级集合,如下所示:

Firestore-root
    |
    --- questions (collections)
    |     |
    |     --- questionId (document)
    |            |
    |            --- questionId: "LongQuestionIdOne"
    |            |
    |            --- title: "Question Title"
    |
    --- tags (collections)
          |
          --- tagIdOne (document)
          |     |
          |     --- tagId: "yR8iLzdBdylFkSzg1k4K"
          |     |
          |     --- tagName: "History"
          |     |
          |     --- questionId: "LongQuestionIdOne"
          |     |
          |     --- //Other tag properties
          |
          --- tagIdTwo (document)
                |
                --- tagId: "tUjKPoq2dylFkSzg9cFg"
                |
                --- tagName: "Geography"
                |
                --- questionId: "LongQuestionIdTwo"
                |
                --- //Other tag properties

这两种方法的区别在于:

  • 如果您想查询数据库以获取特定问题的所有tags,使用第一个模式非常容易,因为只需要CollectionReference(问题 -> questionId -> 标签)。要使用第二个模式实现相同的目的,而不是 CollectionReference,需要 Query,这意味着您需要查询整个 tags 集合以仅获取与单个问题对应的标签。
  • 使用第一个模式,一切都更有条理。除此之外,在 Firestore Maximum depth of subcollections: 100。因此,您可以利用这一点。
  • @RenaudTarnec 在他的评论中也提到,Cloud Firestore 中的查询很浅,它们只从运行查询的集合中获取文档。无法在单个查询中从顶级集合和其他集合或子集合中获取文档。 Firestore 不支持一次性跨不同集合进行查询。单个查询只能使用单个集合中文档的属性。因此,您无法使用第一个模式获取所有问题的所有标签。

这种技术称为数据库扁平化,在 Firebase 中是一种非常常见的做法。因此,仅在需要时才使用此技术。因此,在您的情况下,如果您只需要显示单个问题的标签,请使用第一个模式。如果你想以某种方式显示所有问题的所有标签,建议使用第二种模式。

它是否只是为了在您的文档接近 1MB 限制时进行扩展?

如果您在文档中有对象的子集合,请注意子集合的大小不计入 1 MiB 限制。仅计算存储在文档属性中的数据。

2019 年 10 月 1 日编辑:

根据@ShahoodulHassan 的评论:

那么你没有办法使用第一个模式获取所有问题的所有标签吗?

其实现在有,我们可以使用Firestore collection group query获取所有问题的所有标签。需要注意的一点是,所有子集合必须具有相同的名称,例如 tags

【讨论】:

  • 所以本质上归结为请求子集合更快,而查询主集合更灵活
  • @HamishJohnson 速度相同。这完全取决于你想怎么做。我更喜欢第二种选择,以防我以后想在其他东西上使用标签。但是,它确实增加了定价。
  • @Alex Mamo So there is no way you can get all the tags of all the questions using the first schema. 对名为“tags”的集合的集合组查询现在不能解决这个问题吗?
  • @ShahoodulHassan 作为一种影响,使用firestore collection group query。请注意,所有子集合都应具有相同的名称。我会更新我的答案。
  • @MobileMon 不,成本没有区别。您只需为从查询中收到的文档付费。如果子集合组查询返回与主​​集合相同数量的文档,则成本相同。
【解决方案2】:

我发现子集合的最大优势是它们有自己的写入速率限制,因为每个子集合都有自己的索引(假设您没有 集合组 em> 索引)。这对于小型应用程序可能不是问题,但对于中/大型应用程序来说可能非常重要。

想象一个聊天应用程序,其中每个聊天都有一系列消息。您需要按时间戳索引消息以按时间顺序显示它们。 Firestore 对顺序值 is 500/second 的写入限制,这绝对是中型应用程序所能达到的(特别是如果您考虑到流氓用户编写消息的可能性——目前使用安全规则不容易防止)

// root collection

/messages {
  chatId: string
  timeSent: timestamp // the entire app would be limited to 500/second
}
// sub-collection

/chat/{chatId}/messages {
  timeSent: timestamp // each chat could safely write up to 500/second
}

【讨论】:

  • 没错,但另一方面,如果你想找到一个特定的标签,你需要存储标签的 id 和根集合的 id(s) .
  • 事后看来,我的回答很糟糕。自从我最初写它以来,我已经改变了我的看法,所以我会更新它。大规模子集合有一些重要的优势
  • 我期待听到它,因为随着时间的流逝,我对子收藏的热情越来越低,以至于我后悔曾经使用过它们。很高兴听到另一个观点
  • 好点!这是我过去遇到过的事情。是的,它看起来确实像将它作为一个集合组将其编入索引。干杯
【解决方案3】:

很惊讶这之前没有被提及,但是子集合可以(在某些情况下)帮助绕过 orderBy 限制:

您不能按包含在等式 (==) 或 in 子句中的字段对查询进行排序。

假设您想获取 用户最近 10 次登录

顶级:

//We can't use .orderBy after .where('==')
USER_LOGINS.where('userId', '==', {uid}).limit(10) 

子集合:

//With a subcollection we can order and limit properly
USERS.doc({uid}).collection('LOGINS').orderBy('unixCreated', 'desc').limit(10);

【讨论】:

  • 你确定吗?从该行的编写方式来看,听起来您不能按 where 子句中的字段排序。所以你不能按 userId 订购(我不知道你为什么要这样做),但你可以按 unixCreated 订购
猜你喜欢
  • 2023-03-16
  • 2018-12-02
  • 2011-04-24
  • 1970-01-01
  • 2014-10-26
  • 1970-01-01
  • 2012-02-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多