Nodejs mongoose中自建的_id是怎么生成的?

Nodejs mongoose中自建的_id是怎么生成的?

从官方mongoose v3.8.7的手册中看到关于Schema的_id属性,发现这个属性在new一个模型的时候就已经生成了,这个时候根本就没有和MongoDB通信! 我好奇mongoose是如何生成这个_id的值的,它能保证唯一性么?

另外,官方提到可以关闭这个特性,但是

var schema = new Schema({ name: String }, { _id: false });
var Page = mongoose.model('Page', schema);
var p = new Page({ name: 'mongodb.org' });
console.log(p); // { name: 'mongodb.org' }

// MongoDB will create the _id when inserted p.save(function (err) { if (err) return handleError(err); Page.findById(p, function (err, doc) { if (err) return handleError(err); console.log(doc); // { name: ‘mongodb.org’, _id: ‘50341373e894ad16347efe12’ } }) })

实际测试发现根本就不会save成功,会提示:

[Error: document must have an _id before saving]

请问如何关闭_id后实现保存?


10 回复

Node.js Mongoose 中自建的 _id 是怎么生成的?

背景介绍

在使用 Mongoose 构建 Node.js 应用时,经常会遇到 _id 的问题。Mongoose 默认会在每个文档中自动创建并管理 _id 字段,该字段通常用于唯一标识数据库中的每一条记录。但是,如果你希望自定义 _id 或者禁用默认的 _id 功能,可能会遇到一些挑战。

_id 的生成方式

在 Mongoose 中,_id 字段默认是一个 ObjectId 类型。ObjectId 是一种特殊的字符串格式,它由12个字节组成(即24个十六进制字符),包含了时间戳、机器标识符、进程ID和随机数等信息,以确保其唯一性。

如何关闭 _id

如果你想禁用 Mongoose 自动为每个文档生成 _id,可以通过在 Schema 定义中设置 { _id: false } 来实现。然而,需要注意的是,这样做会导致某些功能失效,比如 findById 方法。

var schema = new Schema({ name: String }, { _id: false });
var Page = mongoose.model('Page', schema);

// 创建一个新的页面对象
var p = new Page({ name: 'mongodb.org' });

// 尝试保存这个对象
p.save(function (err) {
  if (err) return console.error(err);
  console.log("保存成功!");
});

上述代码尝试保存一个没有 _id 的文档时,会抛出错误 [Error: document must have an _id before saving],因为 Mongoose 需要 _id 来识别文档。

如何在禁用 _id 后实现保存

如果确实需要禁用 _id 并且仍希望保存数据,可以考虑以下两种方法:

  1. 手动添加 _id: 在保存文档之前,手动为文档添加一个唯一的 _id

    var id = mongoose.Types.ObjectId();
    var p = new Page({ _id: id, name: 'mongodb.org' });
    
  2. 使用自定义 ID 管理器: 使用一个外部服务来生成唯一 ID,并将其赋值给文档。

    const uuid = require('uuid');
    
    var p = new Page({
      _id: uuid.v4(), // 使用 UUID 作为 ID
      name: 'mongodb.org'
    });
    

通过这些方法,你可以在禁用 Mongoose 的 _id 生成功能后,仍然能够成功保存数据到 MongoDB。


关于_id生成,可以看mongodb权威指南2.6.5节。简要说因为mongodb是分布式的,所以就没设计成关系数据库那样自增的主键,例如mysql。 由12字节,按照“时间戳+机器+PID+计数器”,来保证同一时间,同一机器,同一进程,所产生的_id不同(1秒钟可以有1677216不同的_id)。如果是分布式情况下,这样如果让4个条件全部碰撞的概率是微乎及微。

如果关闭_id后实现保存,不清楚

可能是我表述不够清楚,我是想知道mongoose是如何生存这个_id的,我现在就是不太信任mongoose,所以想关闭它的这个机制,而交给mongoDB来创建_id~~

跟mongoose没关系,就是mongodb对每一条数据都会生成一个唯一id。

但是你不需要建立跟mongodb的连接,mongoose也会帮你创建_id,我就是想关闭mongoose的这个特性而交给mongodb来处理_id的创建工作!

Mongoose 既然该默认这么干,那么它可能使用了跟 mongodb 一样的逻辑在创建这个 _id。

_id 的作用只是为了保证不重复而已啊,这跟 UUID 一样。只要算法的逻辑是一样的,那么不管在 mongoose 层面生成还是在 mongodb 层面生成,我认为这都是无须担心的。

至于为何在 mongo shell 中可以让 mongodb 自动生成 _id,有没有可能是这样:其实 mongodb 并不是在你保存后自动帮你生成了 _id,而是 mongo shell 在 save 前帮你生成了 _id,再存入 mongodb?

mysql 的自增不好在客户端做,所以我们认为在服务端做是更合适的。但是保证一段 string 的不重复,在客户端和服务端做都是有保证的。

况且,mongoose 之所以允许你把 _id 设为不自动生成。也并不是说,不添加 _id 就能存入 mongodb 让 mongodb 自动生成 _id。它只是方便某些自定义 _id (比如 email、身份证号)的逻辑更方便而已。

恩,我认为你分析的很合理,补充一下,mongoose提供禁止_id的开关,我看github上的bug列表中有人提到是为了适应内嵌文档的。

like this:

var childSchema = new Schema({ name: 'string'}, {_id: false});

var parentSchema = new Schema({ children: [childSchema] })

在 Mongoose 中,_id 字段默认是自动创建的,并且其值通常是 ObjectId 类型。ObjectId 是一个全局唯一的标识符,由时间戳、机器标识、进程 ID 和随机数组成,这确保了它的唯一性。

如果你想自定义 _id 的生成方式或完全禁用它,可以通过配置 Schema 来实现。但如果你禁用了 _id,那么在保存文档时需要手动设置 _id 字段,否则会抛出错误。

示例代码

禁用 _id 并手动设置

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

// 创建一个新的 Schema
const schema = new Schema({ name: String }, { _id: false });

// 自定义 _id 的生成方式
schema.add({
  _id: {
    type: String,
    default: () => {
      return mongoose.Types.ObjectId().toString();
    }
  }
});

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

const page = new Page({ name: 'mongodb.org', _id: mongoose.Types.ObjectId().toString() });

page.save((err) => {
  if (err) {
    console.error(err);
  } else {
    console.log('Document saved successfully!');
  }
});

解释

  1. 禁用 _id: 通过 { _id: false } 关闭默认的 _id 生成。
  2. 添加自定义 _id: 使用 schema.add 方法添加一个自定义的 _id 字段,并使用默认值函数生成一个唯一的 ObjectId。
  3. 保存文档: 在保存文档之前,必须手动设置 _id 字段,以避免出现 [Error: document must have an _id before saving] 错误。

通过这种方式,你可以灵活地控制 _id 的生成方式,并且确保文档可以正常保存到数据库中。

回到顶部