Firebase RTD,原子式“;移动“。。。从两处删除并添加“;表格;?

Firebase RTD,原子式“;移动“。。。从两处删除并添加“;表格;?,firebase,firebase-realtime-database,Firebase,Firebase Realtime Database,在Firebase实时数据库中,这是一种非常常见的事务处理方式 “表”A-将其视为“待定” “表”B-将其视为“结果” 某些状态发生时,您需要将项目从A“移动”到B 所以,我的意思是,这很可能是一个云函数在做这件事 显然,此操作必须是原子的,并且必须防止赛道效应等等 因此,对于项目123456,您必须做三件事 读A/123456/ 删除A/123456/ 将值写入B/123456 都是原子的,带锁的 简而言之,Firebase实现这一目标的方法是什么 现在已经有了非常棒的ref.tra

在Firebase实时数据库中,这是一种非常常见的事务处理方式

  • “表”A-将其视为“待定”
  • “表”B-将其视为“结果”
某些状态发生时,您需要将项目从A“移动”到B

所以,我的意思是,这很可能是一个云函数在做这件事

显然,此操作必须是原子的,并且必须防止赛道效应等等

因此,对于项目123456,您必须做三件事

  • 读A/123456/
  • 删除A/123456/
  • 将值写入B/123456
都是原子的,带锁的

简而言之,Firebase实现这一目标的方法是什么

  • 现在已经有了非常棒的ref.transaction系统,但我认为它在这里并不重要

  • 也许是以一种反常的方式使用触发器

IDK


对于在谷歌上搜索的任何人来说,值得注意的是令人难以置信的新Firestore(很难想象有什么比传统Firebase更令人难以置信,但你有它……,新Firestore系统内置了


这个问题是关于好的传统Firebase实时的。

Firebase与字典、a.k.a、键值对一起工作。要更改同一事务中多个表中的数据,您可以使用包含“所有指令”的字典(例如在Swift中)获取基本引用:

let reference = Database.database().reference() // base reference

let tableADict = ["TableA/SomeID" : NSNull()] // value that will be deleted on table A
let tableBDict = ["TableB/SomeID" : true] // value that will be appended on table B, instead of true you can put another dictionary, containing your values
然后,您应该将两个词典合并(如何在此处执行:)为一个,我们称之为
finalDict
, 然后您可以更新这些值,这两个表都将被更新,从A中删除并“移动到”B

实时数据库中没有“表”,所以我将使用术语“位置”来表示包含一些子节点的路径

实时数据库无法在两个不同的位置进行原子化的事务处理。执行事务时,必须选择单个位置,并且只能在该位置下进行更改

您可能认为可以在数据库的根上进行事务处理。这是可能的,但在数据库中任何位置的并发非事务写入操作面前,这些事务可能会失败。这是一项要求,即在事务发生的位置的任何地方都不得有非事务性写入。换句话说,如果您希望在某个位置进行交易,则所有客户机都必须在该位置进行交易,并且没有交易的客户机都不能在该位置进行写操作


如果您在数据库的根目录下进行事务处理,那么这个规则肯定会有问题,因为客户端可能在没有事务的情况下到处写入数据。因此,如果您想执行原子“移动”,您必须让所有客户机在移动的公共根位置始终使用事务,或者接受您不能真正以原子方式执行此操作。

Gustavo的回答允许使用单个API调用进行更新,该调用要么成功,要么失败。而且,由于它不必使用事务,因此争用问题要少得多。它只是从要移动的键加载值,然后写入一个更新

问题是有人可能同时修改了数据。因此,您需要使用安全规则来捕获这种情况并拒绝它。因此,配方变成:

  • 读取源节点的值
  • 在单个
    update()
    调用中删除旧位置时,将值写入其新位置
  • 安全规则会验证操作,包括接受或拒绝操作
  • 如果被拒绝,客户端将从#1重试
  • 这样做本质上是用客户端代码和(一些公认的棘手的)安全规则重新实现Firebase数据库事务

    要做到这一点,更新变得有点棘手。假设我们有这样的结构:

    “key1”:“value1”,
    “键2”:“值2”
    
    我们想将
    value1
    key1
    移动到
    key3
    ,然后Gustavo的方法将发送以下JSON:

    ref.update({
    “key1”:空,
    “键3”:“值1”
    })
    
    何时可以使用以下规则轻松验证此操作:

    “.validate”:
    !data.child(“key3”).exists()&&
    !newData.child(“key1”).exists()&&
    newData.child(“key3”).val()==data.child(“key1”).val()
    "
    
    简言之:

    • key3
      中当前没有值
    • 更新后,
      key1
      中没有值
    • key3
      的新值是
      key1
    这非常有效,但不幸的是,这意味着我们在规则中硬编码了
    key1
    key3
    。为了防止硬编码,我们可以将密钥添加到update语句中:

    ref.update({
    _fromKey:“key1”,
    _托克伊:“钥匙3”,
    键1:null,
    键3:“值1”
    })
    
    不同的是,我们添加了两个具有已知名称的键,以指示移动的源和目标。现在有了这个结构,我们就有了所需的所有信息,我们可以通过以下方式验证移动:

    “.validate”:
    !data.child(newData.child(''u toKey').val()).exists()&&
    !newData.child(newData.child(“'u fromKey').val()).exists()&&
    newData.child(newData.child('u toKey').val()).val()==data.child(newData.child('u fromKey').val()).val()
    "
    
    读起来有点长,但每一行的意思都和以前一样

    在客户端代码中,我们将执行以下操作:

    功能移动(从、到){
    ref.child(from).一次(“值”).然后(函数(快照){
    var value=snapshot.val();
    更新={
    _fromKey:from,
    _托克伊:到
    };
    更新[from]=null;
    更新[到]=值;
    ref.update(updates).catch(函数(){
    //更新失败,请稍候
    
    reference.updateChildValues(finalDict) // update everything on the same time with only one transaction, w/o having to wait for one callback to update another table