在运行查询之前未创建 Mongoose 索引

Ale*_*ler 2 mongoose mongodb

在运行我的测试时,我收到一个错误,指出我还没有在标题字段上设置文本索引,但在运行我的应用程序时,文本搜索可以很好地使用相同的模型,并且不会抛出错误。

$text 查询所需的文本索引(没有这样的集合“test-db.torrents”)

import mongoose from 'mongoose';
import Category from './category';

const Schema = mongoose.Schema;

const Torrent = new Schema({
    title: {
        type: String
    },
    category: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Category',
        required: true,
        index: true
    },
    size: Number,
    details: [
        {
            type: String
        }
    ],
    swarm: {
        seeders: Number,
        leechers: Number
    },
    lastmod: {
        type: Date,
        default: Date.now()
    },
    imported: {
        type: Date,
        default: Date.now()
    },
    infoHash: {
        type: String,
        unique: true,
        index: true
    }
});

Torrent.index({
    title: 'text'
}, {
    background: false
});

export default mongoose.model('Torrent', Torrent);
Run Code Online (Sandbox Code Playgroud)

我正在使用ava进行测试,这是我的测试用例。

import mongoose from 'mongoose';
import request from 'supertest';
import test from 'ava';
import {makeApp} from '../helpers';

test.before(() => {
    mongoose.Promise = Promise;
    mongoose.connect('mongodb://localhost:27017/astro-test-db');
});

test.after.always(() => {
    mongoose.connection.db.dropDatabase(() => {
        mongoose.disconnect();
    });
});

// Should return [] since HL3 doesn't exist.
test('should return no search results', async t => {
    const app = makeApp();
    const res = await request(app).get(`/api/search?q=HL3`);

    t.is(res.status, 200);
    t.is(res.body.error, {});
    t.is(res.body.torrents.length, 0);
});
Run Code Online (Sandbox Code Playgroud)

这是 ava 的完整输出,因为您可以看到 title 的索引不是使用 'text' 或background: false.

?  astro git:(develop) ? yarn ava test/routes/search.js -- --verbose
yarn ava v0.24.6
$ "/Users/xo/code/astro/node_modules/.bin/ava" test/routes/search.js --verbose

Mongoose: categories.ensureIndex({ title: 1 }, { unique: true, background: true })
Mongoose: torrents.ensureIndex({ category: 1 }, { background: true })
Mongoose: torrents.count({}, {})
Mongoose: categories.ensureIndex({ slug: 1 }, { unique: true, background: true })
Mongoose: torrents.find({ '$text': { '$search': 'x' } }, { limit: 100, sort: { score: { '$meta': 'textScore' }, 'swarm.seeders': -1 }, fields: { score: { '$meta': 'textScore' } } })
  ? should return no search results 

  1 test failed [13:59:07]

  should return no search results
  /Users/xo/code/astro/test/routes/search.js:24

   23:     t.is(res.status, 200);            
   24:     t.is(res.body.error, {});         
   25:     t.is(res.body.torrents.length, 0);

  Difference:

    - Object {
    -   code: 27,
    -   codeName: "IndexNotFound",
    -   errmsg: "text index required for $text query (no such collection \'astro-test-db.torrents\')",
    -   message: "text index required for $text query (no such collection \'astro-test-db.torrents\')",
    -   name: "MongoError",
    -   ok: 0,
    - }
    + Object {}

  _callee$ (test/routes/search.js:24:7)
  tryCatch (node_modules/regenerator-runtime/runtime.js:65:40)
  Generator.invoke [as _invoke] (node_modules/regenerator-runtime/runtime.js:303:22)
  Generator.prototype.(anonymous function) [as next] (node_modules/regenerator-runtime/runtime.js:117:21)
  step (test/routes/search.js:19:191)

error Command failed with exit code 1.
Run Code Online (Sandbox Code Playgroud)

Nei*_*unn 5

您应该确保索引是在“前台”中创建的,因为“后台”创建是默认的。

Torrent.index({
    title: 'text'
},{ "background": false });
Run Code Online (Sandbox Code Playgroud)

至少对于您的测试,否则有可能在创建索引之前运行查询。设置{ background: false }确保索引在其他操作可以运行之前就已经存在。这与MongoDB的默认行为相反,因此需要进行显式设置。

在生产环境中,通常最好通过其他方式部署索引。“前台”创建也会导致较小的索引大小,但当然会“阻止”IO,但在生产中至少执行一次通常仍然更好。

来自文档的引用

默认情况下,MongoDB 在前台构建索引,这会在索引构建时阻止对数据库的所有读写操作。此外,在前台索引构建期间不会发生需要对所有数据库(例如 listDatabases)进行读或写锁定的操作。

这意味着发生这种情况时不会发生读取或写入。因此,在“前台”创建模式下创建索引时,无法插入数据并且无法运行查询。

至于大小,在引文的同一页上再往下一点:

后台索引构建需要更长的时间才能完成,并导致索引最初比在前台构建的索引更大或更不紧凑。随着时间的推移,后台构建的索引的紧凑性将接近前台构建的索引。

因此,您可以在后台创建索引,这些索引将“随着时间的推移”在生产环境中缩小为更紧凑的大小。但是出于测试和开发目的,您的默认设置应该始终是在“前台”中创建,以免被时间问题所困扰。


作为一个最小的测试用例:

var mongoose = require('mongoose'),
    Schema = mongoose.Schema;

mongoose.set('debug', true);

var testSchema = new Schema({
  title: { type: String }
});

testSchema.index({
  title: 'text'
},{ background: false });

var Test = mongoose.model('Test', testSchema);

mongoose.connect('mongodb://localhost/texttest');

Test.create({ title: 'something here' },function(err,doc) {

  Test.find({ "$text": { "$search": "something" } },function(err,doc) {
    if (err) throw err;
   console.log(doc);

   Test.collection.drop(function(err) {
    if (err) throw err;
    mongoose.disconnect();
   });
  });
});
Run Code Online (Sandbox Code Playgroud)

作为替代方法,自动关闭猫鼬自动索引功能并手动设置,然后通过.ensureIndexes()以下方式手动调用创建:

var async = require('async'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

mongoose.set('debug', true);

var testSchema = new Schema({
  title: { type: String }
},{ autoIndex: false });

testSchema.index({
  title: 'text'
},{ background: false });

var Test = mongoose.model('Test', testSchema);

mongoose.connect('mongodb://localhost/texttest');

// Manually set indexing to on
Test.schema.options.autoIndex = true;
//console.log(Test.schema.options);

Test.ensureIndexes(function(err) {
  if (err) throw err;

  Test.create({ title: 'something here' },function(err,doc) {

    Test.find({ "$text": { "$search": "something" } },function(err,doc) {
      if (err) throw err;
      console.log(doc);

      Test.collection.drop(function(err) {
        if (err) throw err;
        mongoose.disconnect();
      });
    });
  });
});
Run Code Online (Sandbox Code Playgroud)