查询抱怨缺少2dsphere-index,但它就在那里

时间:2017-10-31 13:49:52

标签: node.js mongodb mongoose mongodb-query geospatial

当我执行以下代码时(一个更大的例子,归结为要点)

var mongoose = require("mongoose");

var LocationSchema = new mongoose.Schema({
  userName: String,
  loc: {
    'type': { type: String, enum: "Point", default: "Point" },
    coordinates: { type: [Number] }
  }
})
LocationSchema.index({ category: 1, loc: "2dsphere" });
var Location = mongoose.model("location", LocationSchema);
var mongoDB = 'mongodb://user1:test@ds042417.mlab.com:42417/locationdemo';
mongoose.Promise = global.Promise;
mongoose.connect(mongoDB, { useMongoClient: true });

var testUser = Location({
  userName: "Tester",
  loc: { coordinates: [12.44, 55.69] }
});
testUser.save(function (err) {
  if (err) {
    return console.log("UPPPPs: " + err);
  }
  console.log("User Saved, Try to find him:");

  let query = Location.find({
    loc:
    {
      $near:
      {
        $geometry:
        {
          type: "Point",
          coordinates: [12.50, 55.71]
        },
        $maxDistance: 600000
      }
    }
  })
  query.exec(function (err, docs) {
    if (err) {
      return console.log("Err: " + err);
    }
    console.log("Found: " + JSON.stringify(docs))
  })
});

我收到此错误:

  

错误:MongoError:错误处理查询:ns = locationdemo.locationsTree:GEONEAR字段= loc maxdist = 600000 isNearSphere = 0   排序:{}   Proj:{}    planner返回错误:无法找到$ geoNear查询的索引

但索引在那里(见第10行)和下面的mlab截图。我究竟做错了什么?: enter image description here

2 个答案:

答案 0 :(得分:2)

您违反了一般规则如何使用索引。虽然no restriction确实“2dsphere”索引是复合索引中的“第一”属性,但是“查询”实际上解决了第一个属性非常重要>为了选择索引。

复合索引手册中的Prefixes对此进行了介绍。摘录:

  

{ "item": 1, "location": 1, "stock": 1 }

     

索引具有以下索引前缀:

     
      
  • {item:1}
  •   
  • {item:1,location:1}
  •   
     

对于复合索引,MongoDB可以使用索引来支持对索引前缀的查​​询。因此,MongoDB可以在以下字段中使用索引进行查询:

     
      
  • 项目字段
  •   
  • 项目字段和位置字段
  •   
  • 项目字段,位置字段和库存字段。
  •   
     

但是,MongoDB无法使用索引来支持包含以下字段的查询,因为没有item字段,所列出的字段都不对应于前缀索引:

     
      
  • 位置字段,
  •   
  • 库存字段,或
  •   
  • 位置和库存字段。
  •   

由于您的查询引用"loc" 首先且不包含"category",因此索引不会被选中,MongoDB会返回错误。

因此,为了使用您定义的索引,您还需要实际查询"category"。修改您的商家信息:

var mongoose = require("mongoose");

mongoose.set('debug',true);

var LocationSchema = new mongoose.Schema({
  userName: String,
  category: Number,
  loc: {
    'type': { type: String, enum: "Point", default: "Point" },
    coordinates: { type: [Number] }
  }
})
//LocationSchema.index({ loc: "2dsphere", category: 1 },{ "background": false });
LocationSchema.index({ category: 1, loc: "2dsphere" });

var Location = mongoose.model("location", LocationSchema);
var mongoDB = 'mongodb://localhost/test';
mongoose.Promise = global.Promise;
mongoose.connect(mongoDB, { useMongoClient: true });


var testUser = Location({
  userName: "Tester",
  category: 1,
  loc: { coordinates: [12.44, 55.69] }
});

testUser.save(function (err) {
  if (err) {
    return console.log("UPPPPs: " + err);
  }
  console.log("User Saved, Try to find him:");

  let query = Location.find({
    category: 1,
    loc:
    {
      $near:
      {
        $geometry:
        {
          type: "Point",
          coordinates: [12.50, 55.71]
        },
        $maxDistance: 600000
      }
    }
  })
  query.exec(function (err, docs) {
    if (err) {
      return console.log("Err: " + err);
    }
    console.log("Found: " + JSON.stringify(docs))
  })
});

只要我们包含"category",一切都很好:

  

用户保存,尝试找到他:
  Mongoose:locations.find({loc:{'$ near':{'$ geometry':{type:'Point',coordinates:[12.5,55.71]},'$ maxDistance':600000}},category:1} ,{fields:{}})
  找到:[{“_ id”:“59f8f87554900a4e555d4e22”,“userName”:“Tester”,“category”:1,“__ v”:0,“loc”:{“coordinates”:[12.44,55.69],“type” : “点”}},{ “_ ID”: “59f8fabf50fcf54fc3dd01f6”, “username” 的: “测试”, “类别”:1, “__ v”:0, “LOC”:{ “坐标”:[12.44,55.69] “类型”: “点”}}]

备用案例是“前缀”索引与位置。确保首先删除先前的索引或集合:

LocationSchema.index({ loc: "2dsphere", category: 1 },{ "background": false });

除了你可能应该养成设置"background": true的习惯,否则你会在单元测试中开始遇到竞争条件,在单元测试中,在单元测试代码尝试使用索引之前尚未完成索引。

答案 1 :(得分:0)

我对此问题的第一个解决方案是通过mLab网络界面创建索引,该界面就像魅力一样。

我尝试过Neil建议的解决方案,但仍然失败了。然而,Neil给出的与索引相关的详细说明确实指出了解决问题的方法。 这是一个时间问题(你不总是看到你在本地运行数据库)与我的测试代码执行以下操作相关: 创建索引,创建一个Location文档(第一次也会创建集合),然后在save提供的回调中,我试图找到用户。似乎这里尚未创建索引,这就是错误所在。

如果我将find方法延迟一秒,使用setTimeout就能正常工作。

但是,感谢Neil提供有关使用索引的正确方法的有价值信息(背景): - )