【问题标题】:MongoDB: Atomic Updates & Array ComparisonsMongoDB:原子更新和数组比较
【发布时间】:2014-07-21 09:13:43
【问题描述】:

编辑:解决方案 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]}}
    },
);

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

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

原始问题:

为可领取奖励的游戏编写数据库,数据结构如下:

.
.
"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 端匹配他们想要索取的所有奖励,并将对象分配给他们。

为了强制执行赋值操作的事务性属性,我想出了 2 个解决方案,并且想知道哪一个在性能方面会更好(或者如果我完全错了,就设计或其他)。

解决方案一:

将整个 GameObject[](可能会变大,100 可能 1,000 个对象)本地存储在 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 服务器通信和多次写入开销会害死我。

提前致谢!

【问题讨论】:

    标签: php mongodb performance mongodb-query


    【解决方案1】:

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

    这里的形式意味着$pull 实际上将“查询”对象作为它的参数。最明显的应用是 $in 运算符,其中包含您要删除的每个“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”上使用$in$nin 操作,因此如果数组实际上不包含(或不包含)在$input 数组。

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

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

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

    当然,在最新版本中,您可以修改“偏执狂”的级别并在此处处理 $addToSet 也可以使用 $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 以下。像这样的潜在大“列表”的实际处理本身确实是另一个问题。

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

    【讨论】:

    • Ahhh... 是的,我意识到如果 OutstandingRewards 上的 MongoId 在转换为 GameObjects 时保持持久性,则“偏执查询”会起作用!以前我在创建游戏对象时使用new MongoId(),这会使“偏执查询”的$nin 部分始终返回true。感谢您的回答!
    • 从逻辑上讲,我们实际上只需要GameObjects 上的$nin 对吗?只要至少有 1 个元素匹配,OutstandingRewards[] 上的 $in 就会返回 true 对吗?例如。如果Id:XId:YId:Z 中的 任何一个存在,Id{$in:[X, Y, Z]} 将返回 true,而如果其中任何一个存在,$nin 将返回 false 对吗?
    • 好的,我只是稍微弄乱了查询,以使“偏执查询”绝对严格(根据您的意图),要使用的正确运算符将是 $all $in,对吗?例如。 "OutstandingRewards.Id'" => array( '$all' => $ids ), ...
    猜你喜欢
    • 2016-02-25
    • 2021-04-02
    • 1970-01-01
    • 1970-01-01
    • 2017-06-19
    • 2010-11-08
    • 1970-01-01
    • 2023-03-24
    • 2021-03-09
    相关资源
    最近更新 更多