MongoDB:原子更新&阵列比较

时间:2014-07-21 09:13:43

标签: php mongodb performance mongodb-query

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

db.update(
    { 
        _id: ObjectId("..playerId..."),
        "OutstandingRewards.Id": {$all:[Outstanding, Ids, From PHP]}
    }, 
    {
        $pull: {"OutstandingRewards": {"Id": {$in:[Outstanding, Ids, From PHP]}}},
        $addToSet: {"GameObjects":{$each:[New, Rewards, From, PHP]}}
    },
);

此原子更新可确保我希望在我OutstandingRewards[]删除它们时将所有我想要声明的优秀ID仍然存在,并将相关的已处理奖励添加到GameObjects[]。此外,使用$addToSet运算符可确保不会在GameObjects[]中插入重复的条目。

解决方案基于Neil Lunn的答案如下!

原创Qn:

为具有可索赔奖励的游戏编写数据库,数据结构如下:

.
.
"OutstandingRewards" : [ 
    {
        "Type" : 0,
        "Id" : ObjectId("53c7b54f12727064000000a0")
    }, 
    {
        "Type" : 1,
        "Id" : ObjectId("53c7b54f12727064000000a2")
    },
],
"GameObjects":[
    {
        "Type" : 1,
        "Id" : ObjectId("53c7b54f12727064000000a5")
    },
    {
        "Type" : 3,
        "Id" : ObjectId("53c7b54f12727064000000a9")
    },
],
.
.

Iam尝试实现一个函数,其中我的PHP脚本接收一个Ids数组,用于玩家想要声明的优秀奖励,处理奖励项目,并将新处理的条目插入GameObjects:[]

如果我错了,请纠正我,但由于$elemMatch只返回1个匹配的元素,我只是执行

db.find(
    { _id: ObjectId("..playerId...")}, 
    {_id: 0, "OutstandingRewards": 1, "GameObjects":1}
)

在PHP端匹配他们想要声明的所有奖励,并为他们分配对象。

为了强制执行分配操作的事务属性,我已经提出了2个解决方案,并且想知道哪个性能更好(或者如果我认为这完全错误,那么设计条款或其他方式)。

解决方案1:

在PHP数组中本地存储整个GameObject[](可能会增长大,100个可能是1,000个对象),将任何声明的奖励附加到此数组并同时重写整个GameObject[] { {1}}对象被删除:

OutstandingRewards[]

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

解决方案2:

对于用户想要声明的每个奖励,请执行“查找和修改”操作。拨打:

GameObjects[]

我担心在这种情况下,PHP-MongoDb服务器通信和多次写入开销将会扼杀我。

提前致谢!

1 个答案:

答案 0 :(得分:2)

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

此处的表单意味着$pull实际上将“查询”对象作为其参数。这里最明显的是$each运算符,其中包含要删除的每个“Id”值。

因此,如果您的“选择列表”恰好恰好是当前的“Oustanding”项目(已经“反序列化”到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”上使用$in$in操作,因此如果数组实际上不包含(或不包含)对象,则查询实际上不会匹配在$input数组中显示。

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

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

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

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

var input; // the same JavaScript object
var ids = input.map(function(x) { return x.Id } );

db.collection.findAndModify({
    "query": {
        "_id": player,
        "OutstandingRewards.Id": { "$in": ids }
    },
    "update": {
        "$pull": {
            "OutstandingRewards": {
                "Id": { "$in": ids }
            }
        },
        "$addToSet": {
            "GameObjects": { "$each": input }
        }
    },
    "new": true
})

正如最后一点,即使你可以在某种程度上,将数组无限期地扩展或特别大的数字通常也不是一个好主意。当然,永远不会为文档打破16MB,并且在性能ID受到显着影响之前,确实应该保持在500以下。实际处理可能很大的“列表”,例如这本身就是另一个问题。

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