在Firebase实时数据库中,这是一个非常常见的事务性事务
某些状态发生,您需要将项目从A“移动”到B.
所以,我当然意味着这可能是一个云功能。
显然,这个操作必须是原子的,你必须防止赛道效应等等。
因此,对于项目123456,您必须做三件事
所有原子,带锁。
简而言之,Firebase实现这一目标的方式是什么?
IDK
对于在这里搜索的人来说,值得注意的是,令人难以置信的新Firestore(很难想象有什么东西比传统的Firebase更令人难以置信,但你有它......),新的Firestore系统已经内置了。 ......
这个问题是关于旧的传统Firebase Realtime。
Gustavo的答案允许使用单个API调用进行更新,该调用可以完成成功或失败。而且由于它不必使用事务,因此争用问题要少得多。它只是加载它想要移动的键的值,然后写入一个更新。
问题是有人可能在此期间修改了数据。因此,您需要使用安全规则来捕获这种情况并拒绝它。所以食谱变成:
update()
调用中删除旧位置这样做基本上是使用客户端代码和(一些公认的棘手的)安全规则重新实现Firebase数据库事务。
为了能够做到这一点,更新变得有点棘手。假设我们有这样的结构:
"key1": "value1",
"key2": "value2"
我们想将value1
从key1
移动到key3
,然后Gustavo的方法会发送这个JSON:
ref.update({
"key1": null,
"key3": "value1"
})
何时可以使用以下规则轻松验证此操作:
".validate": "
!data.child("key3").exists() &&
!newData.child("key1").exists() &&
newData.child("key3").val() === data.child("key1").val()
"
用语言:
key3
没有价值。key1
没有任何价值key3
的新值是key1
的当前值这很有效,但不幸的是,我们在规则中对key1
和key3
进行了硬编码。为了防止对它们进行硬编码,我们可以将键添加到更新语句中:
ref.update({
_fromKey: "key1",
_toKey: "key3",
key1: null,
key3: "value1"
})
不同的是,我们添加了两个具有已知名称的键,以指示移动的源和目的地。现在有了这个结构,我们可以获得所需的所有信息,我们可以通过以下方式验证移动:
".validate": "
!data.child(newData.child('_toKey').val()).exists() &&
!newData.child(newData.child('_fromKey').val()).exists() &&
newData.child(newData.child('_toKey').val()).val() === data.child(newData.child('_fromKey').val()).val()
"
读取时间稍长,但每行仍然与以前相同。
在我们的客户端代码中:
function move(from, to) {
ref.child(from).once("value").then(function(snapshot) {
var value = snapshot.val();
updates = {
_fromKey: from,
_toKey: to
};
updates[from] = null;
updates[to] = value;
ref.update(updates).catch(function() {
// the update failed, wait half a second and try again
setTimeout(function() {
move(from, to);
}, 500);
});
}
move ("key1", "key3");
如果您想要玩这些规则的代码,请查看:https://jsbin.com/munosih/edit?js,console
实时数据库中没有“表”,因此我将使用术语“位置”来引用包含一些子节点的路径。
实时数据库无法在两个不同的位置进行原子事务处理。执行交易时,您必须选择一个位置,并且您只能在该单个位置进行更改。
您可能认为您只能在数据库的根目录进行交易。这是可能的,但是面对数据库中任何地方的并发非事务写入操作,这些事务可能会失败。要求在事务发生的位置任何地方都不得进行非事务性写入。换句话说,如果您想在某个位置进行交易,那么所有客户都必须在那里进行交易,没有客户可以在没有交易的情况下在那里进行交易。
如果您在数据库的根目录进行交易,那么这条规则肯定会有问题,客户端可能在没有事务的情况下在整个地方写入数据。因此,如果您想要执行原子“移动”,您必须让所有客户端始终在公共根位置使用事务进行移动,或者接受您不能真正原子地执行此操作。
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
然后你应该合并(如何在这里:How do you add a Dictionary of items into another Dictionary)将两个字典合并为一个,让我们称之为finalDict
,然后你可以更新这些值,并且两个表都将被更新,从A中删除并“移动到”B
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