MongoDB 基础系列八:数据建模之 References

前言

此篇博文是 Mongdb 基础系列之一;主要介绍 MongoDB 的数据建模相关内容;

本章将会更为深入的去聊一聊有关 Reference 建模的相关话题;

本文为作者的原创作品,转载需注明出处;

简介

MongoDB 并不支持 join 操作,在 MongoDB 中通常使用 denormalized 的方式,就是将关联的数据存储在一个 document 中来尽量避免 join 操作;但是,往往,在某些特殊的情况,必须需要消除大量冗余数据的情况下,比如前文所描述到的 One-to-Many Relationships with Document References 中所描述到的例子那样,我们还是必须要使用 References 的建模方式,既是 normalized 的建模方式;

MongoDB 提供了两种方式来对 References 的情况进行建模,

  • Manual references
    这种是最常见的方式,直接通过在另外一个 document 保存其引用的 document 的 _id 即可;当要查询被引用的 document 的时候,只需要执行一个新的查询,根据该 reference _id 即可查询得到被引用的 document;

  • DBRefs
    在某些情况下,如果 document $A$ 所要引用的 document $B$ 在另外一个 collection 或者是 database 中 ( sharding 以后 ),仅仅通过一个 _id 从 $A$ 中去查询 $B$ 效率会非常的慢,因为是全局搜索;所以,为了提升性能,便出现了 DBRefs,它也是一个外键,但不同于直接使用 _id,它会使用三个元素来构建其 DBRefs 外键,$B$ 的 _id,$B$ 的 collection name,可选的 database name;这样,在跨 collection,跨 database 的情况下,查询效率就会极大的得到提升;

    同样,为了通过 DBRefs 外键来获取所引用的对象,需要许多额外的查询来进行,不过,因为这些查询往往都是有共性的,所以大量的第三方 driver都提供了相关的 helper 类;

除非,你有令人信服的理由使用 DBRefs,否则使用 Manual Reference

Manual reference

Background

Using manual references is the practice of including one document’s _id field in another document. The application can then issue a second query to resolve the referenced fields as needed.

Process

Consider the following operation to insert two documents, using the _id field of the first document as a reference in the second document:

1
2
3
4
5
6
7
8
9
10
11
12
13
original_id = ObjectId()

db.places.insert({
"_id": original_id,
"name": "Broadway Center",
"url": "bc.example.net"
})

db.people.insert({
"name": "Erin",
"places_id": original_id,
"url": "bc.example.net/Erin"
})

Then, when a query returns the document from the people collection you can, if needed, make a second query for the document referenced by the places_id field in the places collection.

这里仅仅需要记住一点,就是,如果要通过对象 people 去获取 place 对象,那么需要额外新建一个查询根据 places_id 来进行查询;

Use

For nearly every case where you want to store a relationship between two documents, use manual references. The references are simple to create and your application can resolve references as needed.

The only limitation of manual linking is that these references do not convey the database and collection names. If you have documents in a single collection that relate to documents in more than one collection, you may need to consider using DBRefs.

唯一的缺陷是,manual references 不传递 database 和 collection names;如果你的 document 需要引用多个不同 collection 中的 documents,那么请考虑使用 DBRefs

DBRefs

Background

DBRefs are a convention for representing a document, rather than a specific reference type. They include the name of the collection, and in some cases the database name, in addition to the value from the _id field.

Format

$ref

The $ref field holds the name of the collection where the referenced document resides.

注意,$ref 关联的是 collection name;

$id

The $id field contains the value of the _id field in the referenced document.

$db

Optional.

Contains the name of the database where the referenced document resides.

Only some drivers support $db references.

DBRefs 外键格式

1
{ "$ref" : <value>, "$id" : <value>, "$db" : <value> }

该外键由这三个元素所构成;

例子

假设我们有如下的一个 document,该 document 的 creator 是通过一个 DBRefs 进行关联的,关联的是 creators collection 的一条记录(既是 document);

1
2
3
4
5
6
7
8
9
{
"_id" : ObjectId("5126bbf64aed4daf9e2ab771"),
// .. application fields
"creator" : {
"$ref" : "creators",
"$id" : ObjectId("5126bc054aed4daf9e2ab772"),
"$db" : "users"
}
}



最后需要特别注意的是,DBRefs 组合键的顺序是不能打乱的,否则解析 DBRefs 的时候会出错;

Driver Support for DBRefs

更多内容参考 https://docs.mongodb.com/manual/reference/database-references/#dbref-explanation

java

The DBRef class provides support for DBRefs from Java.

python

The Python driver supports DBRefs using the DBRef class and the dereference method.

我的思考与总结

其实,我认为没太有必要去使用 DBRefs,因为 objectid 是全局唯一的,既然是全局唯一的,MongoDB 可以通过其 metadata 快速的根据 objectid 定位到该 document 所在的 collection 和 database,所以,我认为,使用 DBRefs 没太大的必要;除非,MongoDB 没有在其 metadata 中维护相关的信息,但这种可能性非常小,不然它如何做 sharding 呢?

References

https://docs.mongodb.com/manual/reference/database-references/#dbref-explanation