【问题标题】:Firestore transaction with multiple get具有多个 get 的 Firestore 事务
【发布时间】:2018-05-19 17:27:49
【问题描述】:

我正在尝试使用可变数量的读取操作来运行事务。 我把read()操作放在update()之前。

阅读 https://cloud.google.com/firestore/docs/manage-data/transactions 上的 Firestore 文档

“一个事务由任意数量的 get() 操作和任意数量的写操作组成,例如 set()、update() 或 delete()”

使用事务时,请注意:

  • 读操作必须先于写操作。
  • 如果当前编辑影响了一个文档,调用事务的函数(事务函数)可能会运行多次 事务读取。
  • 事务函数不应直接修改应用程序状态。

但没有提供实现。 当我尝试运行下面的代码时,我发现事务函数运行了更多时间,然后我得到了一个异常。 但如果我只尝试一个,一切都会好起来的。

const reservationCol = this.db.firestore.collection('reservations');
        return this.db.firestore.runTransaction(t => {
         return Promise.all([
            t.get(reservationCol.doc('id1')),
            t.get(reservationCol.doc(('id2')))]
        ).then((responses) => {

        let found = false;
        responses.forEach(resp => {
               if (resp.exists)
                    found = true;
         });
         if (!found)
         {
               entity.id='id1';
               t.set(reservationCol.doc(entity.id), entity);
               return Promise.resolve('ok');
          }
          else
              return Promise.reject('exist');
         });
    });

【问题讨论】:

  • 你发现了吗?我也有同样的问题。就我而言,我有一个未知数量的firestore引用数组,我需要获取每个引用,然后为每个引用添加+1并全部更新。他们肯定需要文档中的多次获取示例。
  • 不,我没有更新它,我使用另一个包含所有信息的结构解决了这个问题。这样我就可以单读了。

标签: transactions angularfire2 google-cloud-firestore


【解决方案1】:

Firestore 文档没有这样说,但答案隐藏在 API 参考中:https://cloud.google.com/nodejs/docs/reference/firestore/0.13.x/Transaction?authuser=0#getAll

您可以使用Transaction.getAll() 而不是Transaction.get() 来获取多个文档。您的示例将是:

const reservationCol = this.db.firestore.collection('reservations');
return this.db.firestore.runTransaction(t => {
  return t.getAll(reservationCol.doc('id1'), reservationCol.doc('id2'))
    .then(docs => {
      const id1 = docs[0];
      const id2 = docs[1];
      if (!(id1.exists && id2.exists)) {
        // do stuff
      } else {
        // throw error
      }
    })
}).then(() => console.log('Transaction succeeded'));

【讨论】:

【解决方案2】:

我无法弄清楚如何在纯 Typescript 中执行此操作,但我能够找到一个使用 Promise 的 JavaScript example,因此我对其进行了调整以满足我的需要。它似乎工作正常,但是当我快速运行我的功能(通过快速连续单击按钮)时,我收到显示为POST https://firestore.googleapis.com/v1beta1/projects/myprojectname/databases/(default)/documents:commit 400 () 的控制台错误。我不清楚这些是我应该担心的错误,还是仅仅是事务重试的结果。我发布了my own question about that,并希望得到一些答案。同时,这是我想出的代码:

async vote(username, recipeId, direction) {

  let value;

  if ( direction == 'up' ) {
    value = 1;
  }

  if ( direction == 'down' ) {
    value = -1;
  }

  // assemble vote object to be recorded in votes collection
  const voteObj: Vote = { username: username, recipeId: recipeId , value: value };

  // get references to both vote and recipe documents
  const voteDocRef = this.afs.doc(`votes/${username}_${recipeId}`).ref;
  const recipeDocRef = this.afs.doc('recipes/' + recipeId).ref;

  await this.afs.firestore.runTransaction( async t => {

    const voteDoc = await t.get(voteDocRef);
    const recipeDoc = await t.get(recipeDocRef);
    const currentRecipeScore = await recipeDoc.get('score');

    if (!voteDoc.exists) {

      // This is a new vote, so add it to the votes collection
      // and apply its value to the recipe's score
      t.set(voteDocRef, voteObj);
      t.update(recipeDocRef, { score: (currentRecipeScore + value) });

    } else {

      const voteData = voteDoc.data();

      if ( voteData.value == value ) {

        // existing vote is the same as the button that was pressed, so delete
        // the vote document and revert the vote from the recipe's score
        t.delete(voteDocRef);
        t.update(recipeDocRef, { score: (currentRecipeScore - value) });

      } else {

        // existing vote is the opposite of the one pressed, so update the
        // vote doc, then apply it to the recipe's score by doubling it.
        // For example, if the current score is 1 and the user reverses their
        // +1 vote by pressing -1, we apply -2 so the score will become -1.
        t.set(voteDocRef, voteObj);
        t.update(recipeDocRef, { score: (currentRecipeScore + (value*2))});
      }

    }

    return Promise.resolve(true);

  });

}

【讨论】:

    【解决方案3】:

    我遇到了同样的问题,并决定结合使用批量写入和“正常”读取。 这个决定是基于我需要进行许多不相互依赖的读取这一事实。起初我使用了一种类似于上面 Derrick 提出的方法,但事实证明它对于 may reads 是不可持续的。 代码规定每个循环都阻塞到下一个循环。 我所做的是将所有读取批处理以与Promise.all 并行运行 这样做的缺点是您没有利用事务功能,但是由于我所迭代的领域没有改变,所以这是有道理的 这是我的示例代码

    const batch = firestore().batch()
     const readPromises = invoiceValues.map(val => {
                            return orderCollection(omcId).where(<query field>, '<query operation>', <query>).get()
                        })
    
                        return Promise.all(readPromises).then(orderDocs => {
                      //Perform batch operations here
                            return batch.commit()
                         })
    

    事实证明,这对于许多读取来说更有效,同时保持安全,因为我感兴趣的字段不会改变

    【讨论】:

    • 读取时会不会锁定数据?如果NOT会导致并发问题?
    • “锁定数据”是什么意思?因为我相信锁定发生在数据库级别。所有的操作都是相互独立的,所以万一写的时候有冲突,只会坚持lasp操作。因此,只有保证查询在每次读取中产生唯一结果并且没有“重复值”时,此代码才是安全的
    • 据我了解。事务中发生的情况是:您先读取数据,然后再写入数据库,事务将通过应用一定的“锁定”来保证数据在您读取之后和写入之前不会更改。通过这种方式,您可以确信在更新完成之前您正在阅读最新信息。但是在这个例子中,不能保证这样的数据一致性吗?如果其他人在您的“获取”方法之后立即执行更新怎么办?
    • 您说的完全正确,不,这不是交易。我没有理解那部分。可以应用此代码的唯一场景是每个操作都是幂等的。
    猜你喜欢
    • 2016-08-06
    • 2019-07-20
    • 1970-01-01
    • 2019-12-30
    • 2018-05-11
    • 2016-05-11
    • 2012-05-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多