Firebase Firestore:如何在强制唯一字段值的同时插入具有自动id的文档?

Firebase Firestore:如何在强制唯一字段值的同时插入具有自动id的文档?,firebase,google-cloud-firestore,firebase-admin,Firebase,Google Cloud Firestore,Firebase Admin,在Firestore中,我有一个包含自动生成ID的文档和一个name属性的水果集合。 我想插入一个新的水果文档,该文档具有自动生成的种子id,并且只有在不存在其他同名文档的情况下才插入。 受此启发,我尝试以下内容: 编辑:对于记录,如公认答案中所述:该代码在交易上不安全:它确实防止了在重载情况下可能插入相同水果名称的竞争条件 const query = firestore.collection(`/fruits`).where("name", "==",

在Firestore中,我有一个包含自动生成ID的文档和一个name属性的水果集合。
我想插入一个新的水果文档,该文档具有自动生成的种子id,并且只有在不存在其他同名文档的情况下才插入。
受此启发,我尝试以下内容:
编辑:对于记录,如公认答案中所述:该代码在交易上不安全:它确实防止了在重载情况下可能插入相同水果名称的竞争条件

const query = firestore.collection(`/fruits`).where("name", "==", "banana").limit(1);

await firestore.runTransaction(async transaction => {
    const querySnapshot = await transaction.get(query);
    if (querySnapshot.length == 0) {
        const newRef = firestore.collection(`/fruits`).doc();
        await transaction.create(newRef, { name: "banana" });
    }
});
但我想知道:newRef保证不被使用吗?
否则,事务是否会自动重试(由于创建失败),直到成功?
否则,我如何插入我的水果

注意:我使用node.js admin SDK,但我认为javascript API也存在同样的问题

编辑:以下是我最后的操作方法:

const hash = computeHash("banana");
const uniqueIndexRef = firestore.doc(`/fruitsNameUniqueIndex/${hash}`);

try {
    await firestore.runTransaction(async transaction => {
        transaction.create(uniqueIndexRef, {});

        const newRef = firestore.collection(`/fruits`).doc();
        transaction.create(newRef, { name: "banana" });
    });
} catch (error) {
    console.log("fruit not inserted", error.message);
}

是否保证不使用newRef

它几乎保证是独一无二的。随机生成两个文档ID的可能性非常小

另见:

您应该注意的一点是,您的代码实际上不是事务安全的。在查询时刻和事务实际创建新文档的时刻之间,没有任何东西可以阻止两个客户端在竞争条件下添加name=banana的新水果。在交通流量低的情况下,这可能没问题,但你是在冒险


事实上,Firestore没有内置的方法来确保文档字段值的唯一性。这需要相当多的额外工作才能自己实现,可能需要将该字段值用作另一个集合中的唯一键,并确保该集合是处理您水果集合中文档的更大事务的一部分。

感谢您提供详细答案。对于生成的id,我将依赖较小的冲突机会。但是关于唯一性,我感到惊讶:在事务中读取我的查询不是应该锁定数据库以防止您描述的竞争条件吗?Firestore不提供任何完整数据库的锁。这根本无法扩展,而且会严重影响性能。您提供的代码只会锁定来自所提供查询的文档。您一次最多可以处理500个单独的文档。哦,我完全搞不清楚交易锁定了什么,但你点燃了我的蜡烛,谢谢!谢天谢地,我只有一个字段要保持唯一,所以我将使用其值的散列作为我的文档id。