MongoDB复合索引使用键和范围条件优化更新

时间:2015-07-29 04:38:12

标签: mongodb indexing mongodb-query

已阅读this doc,它声明索引可以优化更新操作。然后,我正在为我的集合添加一个索引,以优化我正在使用的更新操作。

集合中的记录具有_id对象和时间戳:

{_id: {userId: "sample"}, firstTimestamp: 123, otherField: "abc"}

我想要做的是使用以下查询进行更新:

db.userFirstTimestamp.update(
{_id: {userId: "sample"}, firstTimestamp: {$gt: 100}},
{_id: {userId: "sample"}, firstTimestamp: 100, otherField2: "efg"})

我想存储'第一份文件'基于' firstTimestamp',新旧文档的字段可以不同,因此它不能是$ set查询,它应该改写文档而不是。对于下面的示例" otherField"不应该存在,它应该是" otherField2"代替。

根据我对MongoDB doc和this article的理解,我创建了索引,如下所示

db.sample.createIndex({_id:1, timestamp:1})

然后我尝试使用MongoDB 3.0.4在一个隔离的实验节点上对查询进行基准测试,其规格如下:

  • MongoDB 3.0.4
  • 机器是空的,没有其他操作,只有mongo
  • RAM~30GB
  • 磁盘已剥离RAID 0
  • 收藏有6000万条记录
  • 平均对象大小1001字节
  • 索引大小5.34 gig

当我检查日志时,许多更新查询需要超过100毫秒,而当我执行mongotop时,查询的顶部是写查询,需要大约1000毫秒。它有点慢,因为进行一次查询需要很长时间。

当我做mongostat时,吞吐量仅为每秒400-500次查询

然后我尝试使用查询查询进行查询解释(因为更新不支持解释)

  • 当我不使用投影时,它使用默认索引{_id:1}。
  • 当我仅对_id和timestamp使用投影时,它使用{_id:1,timestamp:1}索引。

我的问题是:

  1. 我创建的索引是否有助于此更新查询?
  2. 如果它没有帮助,那么指数应该如何?
  3. 优化此更新查询的其他任何方式?

1 个答案:

答案 0 :(得分:2)

  1. 有点。但不是最佳的。

  2. 应该是这样的,所以_id键中对象的“元素”索引:

    db.sample.createIndex({ "_id.userId": 1, "timestamp": 1 })
    
  3. 使用$set运算符并停止覆盖您的文档:

    db.sample.update(
        { 
            "_id.userId": "sample", 
            "firstTimestamp": { "$gt": 100 }
        },
        {
            "$set": { "otherfield": "cfg"  }
        }
    )
    
  4. 但实际上你的数据“应该”如下:

    {
        "_id": "sample", 
        "firstTimestamp": 200,
        "otherfield2": "sam"
    }
    

    并更新如下:

        db.sample.update(
            { 
                "_id.userId": "sample", 
                "firstTimestamp": { "$gt": 100 }
            },
            {
                "$set": { 
                    "fistTimetamp": 100,
                    "otherfield2": "efg"
                }
            }
        )
    

    或者,如果你坚持认为“_id”和“firstTimestamp”以外的字段会发生很大变化,那么就这样做:

    {
        "_id": "sample", 
        "firstTimestamp": 200,
        "data": {
            "otherfield2": "sam"
        }
    }
    

    如果您只想替换数据,请执行以下操作:

        db.sample.update(
            { 
                "_id.userId": "sample", 
                "firstTimestamp": { "$gt": 100 }
            },
            {
                "$set": { 
                    "fistTimetamp": 100,
                    "data": {
                       "overwritingField": "efg"
                    }
                }
            }
        )
    

    如果您愿意,可以将“数据”替换为整个对象,或者只更新一个密钥:

        db.sample.update(
            { 
                "_id.userId": "sample", 
                "firstTimestamp": { "$gt": 100 }
            },
            {
                "$set": { 
                    "fistTimetamp": 100,
                    "data.newfield": "efg"
                }
            }
        )
    

    在所有情况下,尝试使用运算符而不是替换整个对象,因为它通常会导致更多流量和更多负载到服务器。

    但总的来说,这里有意义的是“userId”部分“应该”是索引中最能缩小结果的部分。所以它肯定会在时间戳之前进行,其中应该有更多可能的值。

    复合主键很好,但请确保您实际使用它们。奇异值没有任何意义,只能分配给_id。如果你可以在这里查询它们键的一个字段,那么你可能不需要复合对象作为主键。

    更新中的_id表示您获得了_id的完全匹配,因此它不是具有其他键的复合字段。在这种情况下,它应该只是_id本身的值。

    另外一个“范围”是可以的,但是再次考虑你试图匹配一个文件(好吧你没有在任何地方提到“多”),所以再次询问为什么需要它然后去找一个确切的匹配或“至少”上限。

    $set 将“仅”更新您指定的字段。我认为你在输入问题时犯了一个错误,因为“更新”部分的语法无效。但无论如何都要使用更新运算符,因为它们通过发送单个字段或仅打算更新的字段来减少流量。