基于不存在文档的 Firestore 事务(集合级别锁定不可用)

Firestore transaction based on non existent document (Collection level locking not available)

基于 SO 回答我了解到 firestore 在事务中没有集合级别锁定。 就我而言,在写入集合之前,我必须确保用户集合中的用户名字段是唯一的。 为此,我编写了一个执行此操作的事务:

  1. 对用户集合执行查询以检查是否存在用户名=某物的文档
  2. 如果确实存在,则交易失败并 return 错误
  3. 如果不存在,就运行对我要update/create的userId进行写操作update/create。

现在的问题是,如果两个客户端同时尝试 运行 此事务,则两者都可能查询集合,并且由于集合未锁定,一个客户端可能 insert/update 集合中的文档而其他人则看不到。

我的假设正确吗?如果是,那么如何处理这种情况?

您尝试执行的操作实际上不可能以原子方式执行,因为无法在无法使用 ID 识别的文档上安全地进行交易。这里的问题是,只有当您可以 get() 添加或修改特定文档时,交易才是“安全的”。由于您不能 get() 文档使用文档中的字段值,所以您不知所措。

如果您想确保 Firestore 中任何内容的唯一性,则需要将该唯一性编码到文档 ID 本身中。在最简单的情况下,您可以使用用户名作为新集合中文档的 ID。如果这样做,您的交易可以简单地 get() 用户名所需的文件,检查它是否存在,如果不存在则写入该文件。否则,交易可能会失败。

请记住,由于在 Firestore 中有 limitations 记录 ID,如果您的用户名可能违反规则,您可能需要对该用户名进行转义或编码。

将此数据编码到文档 ID 的另一种方法是使用单独的集合作为一种手动索引。然后安全规则可以强制索引的唯一性。所以像这样:

/docs/${documentId} => {uniqueField: "foo", ...}
/docmap/${uniqueField} => {docId: "doc2"}

这里的想法是,在允许他们写入文档之前,必须先写入包含新文档 ID 的文档映射条目。由于 docmap 以我们的唯一字段为关键字,因此它强制执行唯一性。

安全规则大致如下所示:

  function getPath(childPath) {
     return path('/databases/'+database+'/documents/'+childPath)
  }

  // we can only write to our doc if the unique field exists in docmap/
  // and matches our doc id
  match /docs/{docid} {
     let docMapPath = 'docmap/' + request.resource.data.uniqueField;
     allow write: if getData(docMapPath).docId == docId;
     //todo validate data schema
  }

  // It is only possible to add a uniqueField to the docmap
  // if it doesn't already exist for another doc
  // we also validate that the doc id matches our schema
  match /docmap/{uniqueField} {
     allow write: if resource.data.size() == 0 && 
            request.resource.data.docId is string &&
            request.resource.data.docId.size() < 100
  }

写入大致如下所示:

 const db = firebase.firestore();
 db.doc('docmap/foo').set('doc2')
   .then(() => db.doc('docs/doc2').set({uniqueField: 'foo'})
   .then(doc => console.log("success"))
   .catch(e => console.error(e));

您也可以在事务甚至批处理操作中执行此操作以使其成为原子操作,但可能没有必要增加流程的复杂性;安全规则将强制执行约束。