MongoDB 数据库引用

MongoDB 数据库引用(Database Reference)完全指南

核心结论:MongoDB 没有传统外键,但通过 DBRef(数据库引用)标准、手动引用$lookup 聚合 实现灵活的 跨集合、跨数据库 关系。


一、什么是 DBRef?

DBRef 是 MongoDB 官方定义的一种 标准化引用格式,用于在文档中引用另一个集合中的文档。

DBRef 结构

{
  $ref:  "<collection_name>",   // 目标集合名
  $id:   "<document_id>",       // 目标文档 _id
  $db:   "<database_name>"      // 可选:目标数据库名
}

二、DBRef vs 手动引用对比

方式格式优点缺点推荐场景
DBRef{ $ref: "users", $id: ObjectId("..."), $db: "mydb" }标准化、可自动解析复杂、性能略低跨数据库引用、工具支持
手动引用{ user_id: ObjectId("...") }简单、高性能无标准99% 场景推荐
嵌入{ user: { name: "Alice", ... } }原子性、单次查询文档膨胀一对一/一对少量

三、DBRef 实战(Node.js + 原生驱动)

1. 创建带 DBRef 的文档

import { MongoClient, DBRef, ObjectId } from 'mongodb';

const client = new MongoClient('mongodb://localhost:27017');
await client.connect();

const db = client.db('blog');
const posts = db.collection('posts');
const users = db.collection('users');

// 插入用户
const user = await users.insertOne({
  name: 'Alice',
  email: 'alice@example.com'
});

// 创建 DBRef 引用
const post = {
  title: 'MongoDB 数据库引用',
  content: 'DBRef 详解...',
  author: new DBRef('users', user.insertedId, 'blog')  // $db 可选
};

await posts.insertOne(post);

2. 解析 DBRef(手动)

const post = await posts.findOne({ title: 'MongoDB 数据库引用' });

if (post.author instanceof DBRef) {
  const author = await db
    .collection(post.author.collection)
    .findOne({ _id: post.author.oid });

  console.log('作者:', author.name);
}

四、推荐方式:手动引用 + $lookup(生产级)

// 插入(手动引用)
await posts.insertOne({
  title: '推荐方式',
  content: '...',
  author_id: user.insertedId  // 简单 _id
});

// 查询 + JOIN
const result = await posts.aggregate([
  { $match: { title: '推荐方式' } },
  {
    $lookup: {
      from: 'users',
      localField: 'author_id',
      foreignField: '_id',
      as: 'author'
    }
  },
  { $unwind: '$author' },
  {
    $project: {
      title: 1,
      'author.name': 1,
      'author.email': 1
    }
  }
]).toArray();

console.log(result[0]);

输出

{
  "_id": "...",
  "title": "推荐方式",
  "author": {
    "name": "Alice",
    "email": "alice@example.com"
  }
}

五、跨数据库引用(DBRef 唯一优势)

// 文档在 db1.posts
{
  title: '跨库引用',
  // 引用 db2.users 中的文档
  author: {
    $ref: 'users',
    $id: ObjectId('...'),
    $db: 'db2'  // 必须指定
  }
}

查询

const post = await db1.posts.findOne({ title: '跨库引用' });
const author = await client.db('db2')
  .collection(post.author.$ref)
  .findOne({ _id: post.author.$id });

六、Mongoose 中使用 DBRef(不推荐)

const postSchema = new Schema({
  title: String,
  author: {
    type: Schema.Types.DBRef,  // 不推荐
    ref: 'User'
  }
});

警告:Mongoose 官方 不推荐 DBRef,建议用 ref + populate

推荐:Mongoose populate

const postSchema = new Schema({
  title: String,
  author: { type: Schema.Types.ObjectId, ref: 'User' }  // 手动引用
});

const Post = mongoose.model('Post', postSchema);

// 查询 + 自动 JOIN
const post = await Post.findOne().populate('author');
console.log(post.author.name);

七、性能对比

方式查询次数索引要求写入性能推荐指数
嵌入15 stars
手动引用 + $lookup1author_id 索引5 stars
DBRef + 手动解析2author.$id 索引2 stars

八、最佳实践总结

场景推荐方式
同数据库,一对多author_id: ObjectId + $lookup
跨数据库引用DBRef(必须)
读多写少嵌入或反范式(冗余字段)
Mongoose 项目ref + populate
高性能 API手动引用 + 聚合

九、完整示例:博客系统

// 1. 插入用户
const user = await users.insertOne({ name: 'Bob' });

// 2. 插入文章(手动引用)
await posts.insertOne({
  title: 'MongoDB 最佳实践',
  content: '...',
  author_id: user.insertedId,
  tags: ['mongodb', 'nosql'],
  created_at: new Date()
});

// 3. 查询文章 + 作者
const article = await posts.aggregate([
  { $match: { title: 'MongoDB 最佳实践' } },
  {
    $lookup: {
      from: 'users',
      localField: 'author_id',
      foreignField: '_id',
      as: 'author'
    }
  },
  { $unwind: '$author' }
]).next();

十、反范式优化(读性能极致)

// 在文章中冗余作者名(读快,写时同步)
{
  title: '文章',
  author_id: ObjectId('...'),
  author_name: 'Bob',  // 冗余
  author_avatar: 'https://...'
}

更新用户时:需同步更新所有文章(可用 MQ 或变更日志)


总结:黄金法则

法则说明
99% 用手动引用简单、高效
只在跨库时用 DBRef唯一必要场景
$lookup 实现 JOIN聚合管道
populate 在 Mongoose优雅开发
避免 N+1 查询批量 $lookup 或缓存

你现在可以:

// 推荐写法
{ author_id: ObjectId("...") }  // 手动引用

// 查询
$lookup: { from: "users", localField: "author_id", foreignField: "_id", as: "author" }

告诉我你的场景,我帮你设计:

  1. 模型(用户、订单、日志?)
  2. 是否跨数据库
  3. 读写比例

回复 3 个关键词,我生成完整 Schema + 查询代码!

文章已创建 2371

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部