MongoDB 查询优化

MongoDB queries optimisation

我希望从我的用户模型中检索一些信息,如下所示:

var userSchema = new mongoose.Schema({
  email: { type: String, unique: true, lowercase: true },
  password: String,

  created_at: Date,
  updated_at: Date,

  genre : { type: String, enum: ['Teacher', 'Student', 'Guest'] },
  role : { type: String, enum: ['user', 'admin'], default: 'user' },
  active : { type: Boolean, default: false },

  profile: {
    name : { type: String, default: '' },
    headline : { type: String, default: '' },
    description : { type: String, default: '' },
    gender : { type: String, default: '' },
    ethnicity : { type: String, default: '' },
    age : { type: String, default: '' }
  },

  contacts : {
    email : { type: String, default: '' },
    phone : { type: String, default: '' },
    website : { type: String, default: '' }
  },

  location : {
    formattedAddress : { type: String, default: '' },
    country : { type: String, default: '' },
    countryCode : { type: String, default: '' },
    state : { type: String, default: '' },
    city : { type: String, default: '' },
    postcode : { type: String, default: '' },
    lat : { type: String, default: '' },
    lng : { type: String, default: '' }
  }
});

在主页中,我有一个针对 location 的过滤器,您可以在其中浏览来自国家或城市的用户。

所有字段还包含其中的用户数:

United Kingdom
  All Cities (300)
  London (150)
  Liverpool (80)
  Manchester (70)
France
  All Cities (50)
  Paris (30)
  Lille (20)
Nederland
  All Cities (10)
  Amsterdam (10)
Etc...

主页上有这个,然后我还有学生和教师页面,我希望在其中仅了解这些国家和城市有多少教师的信息...

我想做的是创建对 MongoDB 的查询,以通过单个查询检索所有这些信息。

目前查询如下所示:

User.aggregate([
    { 
      $group: { 
        _id: { city: '$location.city', country: '$location.country', genre: '$genre' },
        count: { $sum: 1 }
      }
    },
    {
      $group: { 
        _id: '$_id.country',
        count: { $sum: '$count' },
        cities: { 
          $push: { 
            city: '$_id.city', 
            count: '$count'
          }
        },
        genres: {
          $push: {
            genre: '$_id.genre',
            count: '$count'
          }
        }
      }
    }
  ], function(err, results) {
    if (err) return next();
    res.json({ 
        res: results
    });
  });

问题是我不知道如何获取我需要的所有信息。

是否可以在 Mongo 中通过一次查询获得所有这些信息?

否则:

用 2、3 个不同的请求向 Mongo 创建一些承诺,如下所示:

getSomething
.then(getSomethingElse)
.then(getSomethingElseAgain)
.done

我确信每次指定数据时存储起来会更容易,但是:当数据库中有超过 5000 / 10000 个用户时,它对性能有好处吗?

抱歉,我仍在学习中,我认为这些东西对于理解 MongoDB 性能/优化至关重要。

谢谢

您想要的是一个 "faceted search" 结果,您可以在其中保存有关当前结果集中匹配项的统计信息。随后,虽然有些产品 "appear" 可以在单个响应中完成所有工作,但您必须考虑到大多数通用存储引擎将需要多个操作。

使用 MongoDB,您可以使用两个查询自己获取结果,另一个查询获取分面信息。这将给出与 Solr or ElasticSearch.

等专用搜索引擎产品提供的分面结果类似的结果

但是为了有效地做到这一点,您希望以一种可以有效使用的方式将其包含在您的文档中。您想要的一种非常有效的形式是使用标记化数据数组:

 {
     "otherData": "something",
     "facets": [
         "country:UK",
         "city:London-UK",
         "genre:Student"
     ]
 }

所以 "factets" 是文档中的单个字段,而不是在多个位置。这使得索引和查询变得非常容易。然后,您可以有效地聚合所有结果并获得每个方面的总数:

User.aggregate(
    [
        { "$unwind": "$facets" },
        { "$group": {
            "_id": "$facets",
            "count": { "$sum": 1 }
        }}
    ],
    function(err,results) {

    }
);

或者更理想的是 $match 中的一些标准:

User.aggregate(
    [
        { "$match": { "facets": { "$in": ["genre:student"] } } },
        { "$unwind": "$facets" },
        { "$group": {
            "_id": "$facets",
            "count": { "$sum": 1 }
        }}
    ],
    function(err,results) {

    }
);

最终给出如下回复:

{ "_id": "country:FR", "count": 50 },
{ "_id": "country:UK", "count": 300 },
{ "_id": "city:London-UK", "count": 150 },
{ "_id": "genre:Student": "count": 500 }

这样的结构很容易遍历和检查离散 "country" 和属于 "country" 的 "city" 之类的东西,因为数据只是用连字符一致地分隔"-".

尝试在数组中混合文档是个坏主意。还有 16MB 的 BSON 大小限制也需要遵守,从中混合在一起的结果(特别是如果您试图保留文档内容)很可能最终会在响应中被超过。

对于像从这样的查询中获取 "overall count" 结果这样简单的事情,只需对特定方面类型的元素求和即可。或者只是向 .count() 操作发出相同的查询参数:

User.count({ "facets": { "$in": ["genre:Student"] } },function(err,count) {

});

这里说了,特别是实现"paging"的结果时,那么得到"Result Count"、"Facet Counts"和实际的"Page of Results"的角色都委托给了"separate" 向服务器查询。

将这些查询中的每一个并行提交到服务器,然后组合一个结构以提供给您的模板或应用程序,看起来很像来自提供这种类型的搜索引擎产品之一的分面搜索结果,这没有错的响应。


总结

所以在你的文档中放一些东西来在一个地方标记各个方面。一组标记化的字符串可以很好地用于此目的。对于 "or" 或 "and" 方面选择组合的条件,它也适用于 $in and $all 等查询形式。

不要仅仅为了匹配某些可感知的层次结构而尝试混搭结果或嵌套添加,而是遍历收到的结果并在标记中使用简单的模式。

非常简单

运行 对内容的分页查询作为对构面或总体计数的单独查询。试图将所有内容推送到数组中,然后限制出去只是为了获得计数是没有意义的。这同样适用于 RDBMS 解决方案来做同样的事情,其中​​分页结果计数和当前页面是单独的查询操作。

MongoDB 博客上有更多关于 Faceted Search with MongoDB that also explains some other options. There are also articles on integration with external search solutions using mongoconnector 或其他方法的信息。