Php MongoDB:原子更新&;数组比较

Php MongoDB:原子更新&;数组比较,php,mongodb,performance,mongodb-query,Php,Mongodb,Performance,Mongodb Query,编辑:解决方案3(我正在使用的) 此原子更新可确保在我希望删除所有未完成ID并将相关已处理奖励添加到游戏对象[]时,我希望声明的所有未完成ID仍存在于未完成奖励[]中。此外,使用$addToSet操作符可以确保在GameObjects[]中不会插入重复的条目 基于Neil Lunn以下答案的解决方案 原始Qn: 为具有可申请奖励的游戏编写DB,数据结构如下: . . "OutstandingRewards" : [ { "Type"

编辑:解决方案3(我正在使用的)

此原子更新可确保在我希望删除所有未完成ID并将相关已处理奖励添加到
游戏对象[]
时,我希望声明的所有未完成ID仍存在于
未完成奖励[]
中。此外,使用
$addToSet
操作符可以确保在
GameObjects[]
中不会插入重复的条目

基于Neil Lunn以下答案的解决方案

原始Qn:

为具有可申请奖励的游戏编写DB,数据结构如下:

.
.
"OutstandingRewards" : [ 
    {
        "Type" : 0,
        "Id" : ObjectId("53c7b54f12727064000000a0")
    }, 
    {
        "Type" : 1,
        "Id" : ObjectId("53c7b54f12727064000000a2")
    },
],
"GameObjects":[
    {
        "Type" : 1,
        "Id" : ObjectId("53c7b54f12727064000000a5")
    },
    {
        "Type" : 3,
        "Id" : ObjectId("53c7b54f12727064000000a9")
    },
],
.
.
我正在尝试实现一个函数,在这个函数中,我的PHP脚本为玩家想要领取的未完成奖励接收一系列ID,处理奖励项目,并将新处理的条目插入到
GameObjects:[]

如果我错了,请纠正我,因为
$elemMatch
只返回1个匹配的元素,所以我只执行

db.find(
    { _id: ObjectId("..playerId...")}, 
    {_id: 0, "OutstandingRewards": 1, "GameObjects":1}
)
在PHP端匹配他们想要获得的所有奖励,并为他们分配对象

为了加强分配操作的事务属性,我提出了两种解决方案,并想知道哪一种性能更好(或者如果我在设计或其他方面完全错了)。

解决方案1: 将整个
GameObject[]
(可能会变大,100个或1000个对象)本地存储在一个PHP数组中,将任何声称的奖励附加到此数组中,并重写整个
GameObject[]
,同时删除
OutstandingRewards[]
对象:

db.update({ _id: ObjectId("..playerId...")}, 
    {
        $pull: {"OutstandingRewards": {"Id": {$in:$PHPClaimIdsArray}}},
        $set: {"GameObjects":$PHPNewRewardsArray}
    },
);
我担心这可能会占用大量内存,并且在
GameObjects[]
较大时会产生大量冗余写入

解决方案2: 对于用户想要申请的每个奖励,执行一次“findAndModify”呼叫:

db.findAndModify( 
    query: { 
        _id: ObjectId("..playerId..."),
        "OutstandingRewards": {$elemMatch: {"Id": $rewardId}}
    },
    update: {
        $pull: {"OutstandingRewards": {"Id": $rewardId}},
        $push: {"GameObjects":$rewardObject}
    }
)
在这种情况下,我担心PHP-MongoDb服务器通信和多重写入开销会让我丧命


提前谢谢

我可以为您提供解决方案3,在这种情况下,您将使用从“OustandingRewards”列表中删除所有项目,然后使用修改器,以便立即将这些项目添加到“GameObjects”数组中

这里的形式意味着
$pull
实际上将“query”对象作为其参数。这里要应用的最明显的是带有每个要删除的“Id”值的运算符

因此,如果您的“选择列表”恰好是当前的两个“独立”项(已经“反序列化”到PHP数据),那么您可以执行以下操作:

# $input = array from JSON
# [
#   { "Type" : 0,"Id" : ObjectId("53c7b54f12727064000000a0")}, 
#   { "Type" : 1, Id" : ObjectId("53c7b54f12727064000000a2")}
# ],

function getIds($v)
{
    return $v["Id"];
}

$ids = array_map("getIds",$input);

$collection->findAndModify(
    array( 
        '_id' => $playerId,
        'OutstandingRewards.Id' => array(
            '$in' => $ids
        ),
        'GameObjects' => array( 
            '$nin' => $input
        )
    ),
    array(
        '$pull' => array(
            'OutstandingRewards' => array (
                'Id' => array( '$in' => $ids )
            )
        ),
        '$push' => array( 
            'GameObjects' => (
                '$each' => $input,
                '$slice' => 10000
            )
        )
    ),
    null,
    array( 'new' => true )
)
我以一种特别“偏执”的形式构建了语句的“查询”部分。这意味着它在中同时使用了中的$和分别对“OustandingRewards”和“GameObjects”进行的操作,因此如果数组实际上不包含(或不包含)在
$input
数组中显示的对象,则查询实际上不会匹配

$pull
的参数是使用
$in
删除与该查询匹配的项的“查询”

$push
操作中,
$each
修饰符实际上将数组作为其参数。因此,这意味着该数组中的所有元素都应用于单个“推”操作,而不是使用带有多个更新的循环

在2.6之前的MongoDB版本中,该修饰符是必需的。其目的是“限制”数组在与此类操作一起使用时可能包含的元素数。一般来说,这被认为是一个好主意,但在最新版本中,您不需要它。在这里,它只是被设置为一个你期望大于数组中可能的元素的数字,这样就不会有任何东西被拿走

当然,在最新版本中,您可以在此处修改“偏执狂”的级别和处理方式,也可以使用
$each
修饰符。这一次就像现在的JavaScript一样,PHP语法被解释为:

var输入;//相同的JavaScript对象
var Id=input.map(函数(x){return x.Id});
db.collection.findAndModify({
“查询”:{
“_id”:玩家,
“OutstandingRewards.Id”:{“$in”:ids}
},
“更新”:{
“$pull”:{
“杰出奖励”:{
“Id”:{“$in”:ids}
}
},
“$addToSet”:{
“游戏对象”:{“$each”:输入}
}
},
“新”:真的
})

最后一点要注意的是,尽管在某种程度上可以,但将阵列无限期地扩展或扩展到特别大的数量通常不是一个好主意。当然,文档永远不会超过16MB,而且在性能id明显受到影响之前,应该将其保持在500以下。然而,像这样潜在的大型“列表”的实际处理本身确实是另一个问题


但这至少为您提供了一种方法,可以在单个原子操作中从一个数组中删除和插入多个项,并将它们传输到同一文档中的另一个数组。

Ahhh。。。是的,我意识到如果
杰出奖励
上的mongoid在转换为
游戏对象时保持持久,那么“偏执查询”就可以工作了!以前我在创建游戏对象时使用了
newmongoid()
,这会使“偏执查询”的
$nin
部分始终返回true。谢谢你的回答!从逻辑上讲,我们实际上只需要
游戏对象上的
$nin
,对吗?
OutstandingRewards[]
上的
$in
返回true,只要至少有1个元素匹配,对吗?例如,
Id{$in:[X,Y,Z]}
将返回true,如果
Id:X
Id:Y
Id:Z
中的任何一个存在,那么as
$nin
将返回false,如果它们中的任何一个存在,对吗?好的,我刚刚搞乱了
# $input = array from JSON
# [
#   { "Type" : 0,"Id" : ObjectId("53c7b54f12727064000000a0")}, 
#   { "Type" : 1, Id" : ObjectId("53c7b54f12727064000000a2")}
# ],

function getIds($v)
{
    return $v["Id"];
}

$ids = array_map("getIds",$input);

$collection->findAndModify(
    array( 
        '_id' => $playerId,
        'OutstandingRewards.Id' => array(
            '$in' => $ids
        ),
        'GameObjects' => array( 
            '$nin' => $input
        )
    ),
    array(
        '$pull' => array(
            'OutstandingRewards' => array (
                'Id' => array( '$in' => $ids )
            )
        ),
        '$push' => array( 
            'GameObjects' => (
                '$each' => $input,
                '$slice' => 10000
            )
        )
    ),
    null,
    array( 'new' => true )
)