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);
七、性能对比
| 方式 | 查询次数 | 索引要求 | 写入性能 | 推荐指数 |
|---|---|---|---|---|
| 嵌入 | 1 | 无 | 快 | 5 stars |
手动引用 + $lookup | 1 | author_id 索引 | 快 | 5 stars |
| DBRef + 手动解析 | 2 | author.$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" }
告诉我你的场景,我帮你设计:
- 模型(用户、订单、日志?)
- 是否跨数据库?
- 读写比例?
回复 3 个关键词,我生成完整 Schema + 查询代码!