常见问题解答


Q. 连接到 localhost 时,我收到错误 connect ECONNREFUSED ::1:27017。为什么?

简单的解决方案是将 localhost 替换为 127.0.0.1

出现此错误的原因是 Node.js 18 及更高版本默认情况下优先使用 IPv6 地址而不是 IPv4 地址。此外,大多数 Linux 和 OSX 机器默认情况下在 /etc/hosts 中都有 ::1 localhost 条目。这意味着 Node.js 18 会认为 localhost 指的是 IPv6 ::1 地址。而 MongoDB 默认情况下不接受 IPv6 连接。

您也可以通过 在您的 MongoDB 服务器上启用 IPv6 支持 来修复此错误。


Q. 操作 ... 在 10000 毫秒后超时。这是怎么回事?

A. 根本上,这个问题源于未连接到 MongoDB。您可以在连接到 MongoDB 之前使用 Mongoose,但您必须在某个时间点进行连接。例如

await mongoose.createConnection(mongodbUri).asPromise();

const Test = mongoose.model('Test', schema);

await Test.findOne(); // Will throw "Operation timed out" error because didn't call `mongoose.connect()`
await mongoose.connect(mongodbUri);

const db = mongoose.createConnection();

const Test = db.model('Test', schema);

await Test.findOne(); // Will throw "Operation timed out" error because `db` isn't connected to MongoDB

Q. 我能够在本地连接,但当我尝试连接到 MongoDB Atlas 时,我收到此错误。这是怎么回事?

您必须确保您已在 mongodb 上将您的 IP 列入白名单,以允许 Mongoose 连接。您可以使用 0.0.0.0/0 允许来自所有 IP 的访问。


Q. x.$__y 不是函数。这是怎么回事?

A. 此问题是由于安装了多个相互不兼容的 Mongoose 版本导致的。运行 npm list | grep "mongoose" 以查找并解决问题。如果您将模式或模型存储在单独的 npm 包中,请在您的单独包中将 Mongoose 列在 peerDependencies 中,而不是 dependencies 中。


Q. 我将模式属性声明为 unique,但我仍然可以保存重复项。这是怎么回事?

A. Mongoose 本身并不处理 unique{ name: { type: String, unique: true } } 只是在 name 上创建 MongoDB 唯一索引 的简写。例如,如果 MongoDB 还没有在 name 上创建唯一索引,则以下代码即使在 unique 为 true 的情况下也不会出错。

const schema = new mongoose.Schema({
  name: { type: String, unique: true }
});
const Model = db.model('Test', schema);

// No error, unless index was already built
await Model.create([{ name: 'Val' }, { name: 'Val' }]);

但是,如果您使用 Model.on('index') 事件等待索引构建,则尝试保存重复项将正确地引发错误。

const schema = new mongoose.Schema({
  name: { type: String, unique: true }
});
const Model = db.model('Test', schema);

// Wait for model's indexes to finish. The `init()`
// function is idempotent, so don't worry about triggering an index rebuild.
await Model.init();

// Throws a duplicate key error
await Model.create([{ name: 'Val' }, { name: 'Val' }]);

MongoDB 持久保存索引,因此您只需在从头开始使用新的数据库或运行 db.dropDatabase() 时才需要重新构建索引。在生产环境中,您应该 使用 MongoDB shell 创建索引,而不是依赖于 mongoose 为您执行此操作。模式的 unique 选项便于开发和文档,但 mongoose 不是索引管理解决方案。


Q. 当我在模式中拥有嵌套属性时,mongoose 默认情况下添加空对象。为什么?

const schema = new mongoose.Schema({
  nested: {
    prop: String
  }
});
const Model = db.model('Test', schema);

// The below prints `{ _id: /* ... */, nested: {} }`, mongoose assigns
// `nested` to an empty object `{}` by default.
console.log(new Model());

A. 这是性能优化。这些空对象不会保存到数据库中,也不会出现在结果 toObject() 中,也不会出现在 JSON.stringify() 输出中,除非您关闭 minimize 选项

出现这种行为的原因是 Mongoose 的更改检测和 getter/setter 基于 Object.defineProperty()。为了支持对嵌套属性的更改检测,而不会在每次创建文档时都产生运行 Object.defineProperty() 的开销,mongoose 在编译模型时在 Model 原型上定义属性。因为 mongoose 需要为 nested.prop 定义 getter 和 setter,所以 nested 必须始终在 mongoose 文档上定义为一个对象,即使 nested 在底层 POJO 上未定义。


Q. 我正在为 虚拟中间件getter/setter方法 使用箭头函数,并且 this 的值不正确。

A. 箭头函数 处理 this 关键字的方式不同于传统函数。Mongoose getter/setter 依赖于 this 为您提供对要写入的文档的访问权限,但此功能不适用于箭头函数。如果您不打算在 getter/setter 中访问文档,则不要为 mongoose getter/setter 使用箭头函数。

// Do **NOT** use arrow functions as shown below unless you're certain
// that's what you want. If you're reading this FAQ, odds are you should
// just be using a conventional function.
const schema = new mongoose.Schema({
  propWithGetter: {
    type: String,
    get: v => {
      // Will **not** be the doc, do **not** use arrow functions for getters/setters
      console.log(this);
      return v;
    }
  }
});

// `this` will **not** be the doc, do **not** use arrow functions for methods
schema.method.arrowMethod = () => this;
schema.virtual('virtualWithArrow').get(() => {
  // `this` will **not** be the doc, do **not** use arrow functions for virtuals
  console.log(this);
});

Q. 我有一个名为 type 的嵌入属性,如下所示

const holdingSchema = new Schema({
  // You might expect `asset` to be an object that has 2 properties,
  // but unfortunately `type` is special in mongoose so mongoose
  // interprets this schema to mean that `asset` is a string
  asset: {
    type: String,
    ticker: String
  }
});

但当我尝试保存具有 asset 对象的 Holding 时,mongoose 给出了一个 CastError,告诉我它无法将对象转换为字符串。这是为什么?

Holding.create({ asset: { type: 'stock', ticker: 'MDB' } }).catch(error => {
  // Cast to String failed for value "{ type: 'stock', ticker: 'MDB' }" at path "asset"
  console.error(error);
});

A. type 属性在 mongoose 中是特殊的,因此当您说 type: String 时,mongoose 将其解释为类型声明。在上面的模式中,mongoose 认为 asset 是一个字符串,而不是一个对象。请改用以下方法

const holdingSchema = new Schema({
  // This is how you tell mongoose you mean `asset` is an object with
  // a string property `type`, as opposed to telling mongoose that `asset`
  // is a string.
  asset: {
    type: { type: String },
    ticker: String
  }
});

Q. 我正在填充数组下的嵌套属性,如下面的代码所示

new Schema({
  arr: [{
    child: { ref: 'OtherModel', type: Schema.Types.ObjectId }
  }]
});

.populate({ path: 'arr.child', options: { sort: 'name' } }) 不会按 arr.child.name 排序?

A. 请参阅 此 GitHub 问题。这是一个已知问题,但非常难以解决。


Q. 我模型上的所有函数调用都挂起,我做错了什么?

A. 默认情况下,mongoose 将缓冲您的函数调用,直到它能够连接到 MongoDB。阅读 连接文档的缓冲部分 以获取更多信息。


Q. 如何启用调试?

A. 设置 debug 选项

// all executed methods log output to console
mongoose.set('debug', true);

// disable colors in debug mode
mongoose.set('debug', { color: false });

// get mongodb-shell friendly output (ISODate)
mongoose.set('debug', { shell: true });

有关更多调试选项(流、回调),请参阅 'debug' 选项下的 .set()


Q. 我的 save() 回调从未执行。我做错了什么?

A. 所有 collection 操作(插入、删除、查询等)都会排队,直到 Mongoose 成功连接到 MongoDB。您可能尚未调用 Mongoose 的 connect()createConnection() 函数。

在 Mongoose 5.11 中,有一个 bufferTimeoutMS 选项(默认设置为 10000),它配置 Mongoose 在抛出错误之前允许操作保持缓冲的时间。

如果您想在整个应用程序中选择退出 Mongoose 的缓冲机制,请将全局 bufferCommands 选项设置为 false

mongoose.set('bufferCommands', false);

与其选择退出 Mongoose 的缓冲机制,您可能更愿意将 bufferTimeoutMS 降低,以使 Mongoose 只缓冲很短时间。

// If an operation is buffered for more than 500ms, throw an error.
mongoose.set('bufferTimeoutMS', 500);

Q. 我应该为每个数据库操作创建/销毁一个新的连接吗?

A. 不。在您的应用程序启动时打开您的连接,并在应用程序关闭之前保持打开状态。


Q. 当我使用 nodemon/测试框架时,为什么我会收到 "OverwriteModelError: Cannot overwrite .. model once compiled" 错误?

A. mongoose.model('ModelName', schema) 要求 'ModelName' 唯一,这样您就可以使用 mongoose.model('ModelName') 访问模型。如果您将 mongoose.model('ModelName', schema); 放在 mocha beforeEach() 挂钩 中,则此代码将在每次测试之前尝试创建一个名为 'ModelName' 的新模型,因此您将收到错误。确保您只使用给定名称创建一次新模型。如果您需要创建多个具有相同名称的模型,请创建一个新的连接并将模型绑定到连接。

const mongoose = require('mongoose');
const connection = mongoose.createConnection(/* ... */);

// use mongoose.Schema
const kittySchema = mongoose.Schema({ name: String });

// use connection.model
const Kitten = connection.model('Kitten', kittySchema);

Q. 如何更改 mongoose 将数组路径初始化为空数组的默认行为,以便我在创建文档时可以要求真实数据?

A. 您可以将数组的默认值设置为 undefined

const CollectionSchema = new Schema({
  field1: {
    type: [String],
    default: void 0
  }
});

Q. 如何将数组路径初始化为 null

A. 您可以将数组的默认值设置为 null

const CollectionSchema = new Schema({
  field1: {
    type: [String],
    default: null
  }
});

Q. 为什么我的聚合 $match 在使用日期时无法返回我的 find 查询返回的文档?

A. Mongoose 不会转换聚合管道阶段,因为使用 $project、$group 等,属性的类型可能会在聚合过程中发生变化。如果您想使用聚合框架按日期查询,则有责任确保您传递的是有效的日期。


Q. 为什么对日期对象的原地修改(例如 date.setMonth(1);)不会保存?

doc.createdAt.setDate(2011, 5, 1);
doc.save(); // createdAt changes won't get saved!

A. Mongoose 目前不监视对日期对象的原地更新。如果您需要此功能,请随时在 此 GitHub 问题 上进行讨论。有几种解决方法

doc.createdAt.setDate(2011, 5, 1);
doc.markModified('createdAt');
doc.save(); // Works

doc.createdAt = new Date(2011, 5, 1).setHours(4);
doc.save(); // Works

Q. 为什么在同一个文档上并行调用 save() 多次,只允许第一个保存调用成功并为其余调用返回 ParallelSaveErrors?

A. 由于验证和中间件的异步性质,在同一个文档上并行调用 save() 多次可能会导致冲突。例如,验证,然后随后使同一个路径无效。


Q. 为什么任何 12 个字符的字符串都能成功转换为 ObjectId?

A. 从技术上讲,任何 12 个字符的字符串都是有效的 ObjectId。考虑使用像 /^[a-f0-9]{24}$/ 这样的正则表达式来测试字符串是否正好是 24 个十六进制字符。


Q. 为什么 Mongoose Maps 中的键必须是字符串?

A. 因为 Map 最终存储在 MongoDB 中,而键必须是字符串。


Q. 我正在使用 Model.find(...).populate(...)limit 选项,但返回的结果少于限制。这是怎么回事?

A. 为了避免为 find 查询返回的每个文档执行单独的查询,Mongoose 改用 (numDocuments * limit) 作为限制来进行查询。如果您需要正确的限制,则应使用 perDocumentLimit 选项(Mongoose 5.9.0 中的新增功能)。请记住,populate() 将为每个文档执行单独的查询。


Q. 我的查询/更新似乎执行了两次。这是为什么?

A. 查询重复执行的最常见原因是将回调和 promise 与查询混合使用。这是因为将回调传递给查询函数(如 find()updateOne())会立即执行查询,而调用 then() 会再次执行查询。

将 promise 和回调混合使用会导致数组中出现重复条目。例如,以下代码将 2 个条目插入 tags 数组,*而不是*仅插入 1 个条目。

const BlogPost = mongoose.model('BlogPost', new Schema({
  title: String,
  tags: [String]
}));

// Because there's both `await` **and** a callback, this `updateOne()` executes twice
// and thus pushes the same string into `tags` twice.
const update = { $push: { tags: ['javascript'] } };
await BlogPost.updateOne({ title: 'Introduction to Promises' }, update, (err, res) => {
  console.log(res);
});

还有其他需要添加的内容吗?

如果您想为本页做出贡献,请访问 github 上的此页,并使用编辑 按钮发送拉取请求。