如何在MongoDB中为此特定查询创建索引

时间:2014-10-06 20:59:27

标签: mongodb

我需要为此查询编制索引:

db.messages.find({
  $or: [
    { $and: [
      { receiverFbId: 1 },
      { senderFbId: 2 }
    ]},
    { $and: [
      { receiverFbId: 2 },
      { senderFbId: 1 }
    ]}
  ]
}).sort({ timestamp: -1 });

我创建了索引:

db.messages.ensureIndex({ receiverFbId: 1 });
db.messages.ensureIndex({ senderFbId: 1 });
db.messages.ensureIndex({ receiverFbId: 1, senderFbId: 1, timestamp: -1 });

前两个索引适用于按时间戳排序的查询。第三个索引应该适用于带有排序的查询,但它不适用。使用explain()函数的查询返回BasicCursor。

那么我应该使用按时间戳排序来为此查询编制索引?

2 个答案:

答案 0 :(得分:1)

我通过db.messages.ensureIndex({ receiverFbId: 1, senderFbId: 1, timestamp: -1 }, {name:"rst"});进行了测试。 该索引用于MongoDB V2.6.4,但未在V2.4.8上使用 所以,如果你的MongoDB版本低于2.6,也许你运气不好。 :)

否则,我想说,几乎不可能使用索引来成功查询此查询并完全按时间戳排序,即使在V2.6.4上也是如此。 我在这里举个例子。

在mongo shell上运行以下代码,(均为V2.6.4)

// initialize data
var docs = [ 
// group 1
{
    _id : 1,
    receiverFbId : 1,
    senderFbId : 2,
    timestamp : new Date("2014-10-09")
}, {
    _id : 2,
    receiverFbId : 1,
    senderFbId : 2,
    timestamp : new Date("2014-10-08")
}, {
    _id : 3,
    receiverFbId : 1,
    senderFbId : 2,
    timestamp : new Date("2014-10-07")
}, 
// group 2
{
    _id : 4,
    receiverFbId : 2,
    senderFbId : 1,
    timestamp : new Date("2014-10-08")
}, {
    _id : 5,
    receiverFbId : 2,
    senderFbId : 1,
    timestamp : new Date("2014-10-07")
}, {
    _id : 6,
    receiverFbId : 2,
    senderFbId : 1,
    timestamp : new Date("2014-10-09")
}, 
// group 3
{
    _id : 7,
    receiverFbId : 1,
    senderFbId : 8,
    timestamp : new Date("2014-10-09")
}, {
    _id : 8,
    receiverFbId : 2,
    senderFbId : 6,
    timestamp : new Date("2014-10-01")
} ];

var c = db["messages"];
c.drop();
c.insert(docs);
c.ensureIndex({ receiverFbId: 1, senderFbId: 1, timestamp: -1 }, {name: "rst"});

// make an output test
c.find({
      $or: [
        { $and: [
          { receiverFbId: 1 },
          { senderFbId: 2 }
        ]},
        { $and: [
          { receiverFbId: 2 },
          { senderFbId: 1 }
        ]}
      ]
    }).sort({ timestamp: -1 }); 
// result
{ "_id" : 1, "receiverFbId" : 1, "senderFbId" : 2, "timestamp" : ISODate("2014-10-09T00:00:00Z") }
{ "_id" : 6, "receiverFbId" : 2, "senderFbId" : 1, "timestamp" : ISODate("2014-10-09T00:00:00Z") }
{ "_id" : 2, "receiverFbId" : 1, "senderFbId" : 2, "timestamp" : ISODate("2014-10-08T00:00:00Z") }
{ "_id" : 4, "receiverFbId" : 2, "senderFbId" : 1, "timestamp" : ISODate("2014-10-08T00:00:00Z") }
{ "_id" : 3, "receiverFbId" : 1, "senderFbId" : 2, "timestamp" : ISODate("2014-10-07T00:00:00Z") }
{ "_id" : 5, "receiverFbId" : 2, "senderFbId" : 1, "timestamp" : ISODate("2014-10-07T00:00:00Z") }

// make an explain
c.find({
      $or: [
        { $and: [
          { receiverFbId: 1 },
          { senderFbId: 2 }
        ]},
        { $and: [
          { receiverFbId: 2 },
          { senderFbId: 1 }
        ]}
      ]
    }).sort({ timestamp: -1 }).explain();
// result
{
    "clauses" : [ {
        "cursor" : "BtreeCursor rst",
        "isMultiKey" : false,
        "n" : 3,
        "nscannedObjects" : 3,
        "nscanned" : 3,
        "scanAndOrder" : false,             // Attention on this line
        "indexOnly" : false,
        "nChunkSkips" : 0,
        "indexBounds" : {
            "receiverFbId" : [ [ 1, 1 ] ],
            "senderFbId" : [ [ 2, 2 ] ],
            "timestamp" : [ [ {
                "$maxElement" : 1
            }, {
                "$minElement" : 1
            } ] ]
        }
    }, {
        "cursor" : "BtreeCursor rst",
        "isMultiKey" : false,
        "n" : 3,
        "nscannedObjects" : 3,
        "nscanned" : 3,
        "scanAndOrder" : false,             // Attention on this line
        "indexOnly" : false,
        "nChunkSkips" : 0,
        "indexBounds" : {
            "receiverFbId" : [ [ 2, 2 ] ],
            "senderFbId" : [ [ 1, 1 ] ],
            "timestamp" : [ [ {
                "$maxElement" : 1
            }, {
                "$minElement" : 1
            } ] ]
        }
    } ],
    "cursor" : "QueryOptimizerCursor",
    "n" : 6,
    "nscannedObjects" : 6,
    "nscanned" : 6,
    "nscannedObjectsAllPlans" : 6,
    "nscannedAllPlans" : 6,
    "scanAndOrder" : false,                 // Attention on this line
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "server" : "Duke-PC:27017",
    "filterSet" : false
}

根据上述输出,它几乎符合预期。但我们也可以找到别的东西,

  • 两个组(group 1group 2)已分别被选中并按索引排序。
  • 但是,这两组在timestamp上有交集。 为了提供正确的结果,需要在内存中进行额外的global排序。 这种排序应该非常快,因为每个组都已订购。

我理解"scanAndOrder" : false.explain()的最后一行 它差不多但是not completely实现排序而没有额外的内存排序


---------------编辑------------------

<强>澄清

我之前对scanAndOrder : false.explain()的最后一行的理解是错误的 $or可以完美地合并这些索引的结果,而无需额外的负载缓冲。

感谢Asya Kamsky的帮助。

答案 1 :(得分:0)

似乎排序上的索引只是一个方向,这意味着它可以在同一方向(升序或降序)上对索引进行索引,请参阅this。而且您不需要为同一个前缀放置多个索引。 所以,为了让你的工作做得最好,它应该是这样的:

db.messages.ensureIndex({ receiverFbId: 1, senderFbId: 1 });

  

排序必须为其所有键指定相同的排序方向(即升序/降序)作为索引键模式,或者为其所有键指定反向排序方向作为索引键模式。例如,索引键模式{a:1,b:1}可以支持对{a:1,b:1}和{a:-1,b:-1}的排序,但不支持{a:-1上的排序,b:1}。

因此db.messages.ensureIndex({receiverFbId:1, senderFbId:1, timestamp:-1})根本无效。

当您尝试排序时,请使用以下命令: db.messages.find({...}).sort({timestamp: -1});

要回答您的问题,不,目前它不支持对您建议的排序查询进行索引。如果你真的坚持要做这项工作,你必须将你的时间戳修改为类似(future - timestamp)之类的东西,以使其在升序时可转位,这样它们就可以按相同的方向排序。