代码之家  ›  专栏  ›  技术社区  ›  rendybjunior

MongoDB复合索引,用于使用密钥和范围条件优化更新

  •  2
  • rendybjunior  · 技术社区  · 11 年前

    已阅读 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”存储“firstdocument”,旧文档和新文档的字段可能不同,因此它不能是$set查询,它应该重写文档。对于下面的示例,“otherField”不应存在,而应为“otherField2”。

    基于我对MongoDB文档和 this article ,我创建了如下索引

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

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

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

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

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

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

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

    我的问题是:

    1. 我创建的索引是否有助于此更新查询?
    2. 如果没有帮助,那么索引应该如何?
    3. 还有其他方法可以优化此更新查询吗?
    1 回复  |  直到 11 年前
        1
  •  2
  •   Blakes Seven    11 年前
    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"  }
          }
      )
      

    但实际上,您的数据“应该”是这样的:

    {
        "_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”部分“应该”是索引中缩小结果范围最多的部分。所以它肯定在时间戳之前,时间戳应该有更多可能的值。

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

    你的 _身份证号码 在更新中,建议您获得与 _身份证号码 因此,它不是具有其他键的复合字段。在这种情况下,它应该只是 _身份证号码 它本身

    同样,“范围”是可以的,但再次考虑您正在尝试匹配单个文档(好吧,您没有在任何地方提到“多”),因此再次询问为什么需要它,然后要么进行精确匹配,要么“至少”设置上限。

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

    推荐文章