你真的应该在这里使用数组,因为使用命名对象键确实是
与数据库的基本工作方式相悖。
除了基本的查询问题,例如可能在集合中查找作者“Bob”的所有内容(这对于数组很简单),在查找“最后 10 个”时您会遇到类似的“蛮力”匹配问题.更不用说,作为“非数组”,“最后十个”实际上是什么变得非常主观。
即使假设这些“键”实际上是 MongoDB ObjectID 值的相同生成值(因此是单调的并且总是增加值),但要计算出这些“键”的排序顺序需要蛮力 JavaScript 处理而没有来自集合索引或自然数组索引位置的帮助:
db.collection.mapReduce(
function() {
var messages = this.messages;
var newMessages = Object.keys(this.messages).sort().slice(-10).map(
function(id) {
return messages[id];
}
);
emit(this._id,{ "messages": newMessages });
},
function() {}, // not really reducing anything here
{ "out": { "inline": 1 } }
)
或类似的“时间戳”值(看起来不像时间戳),但这里的基本前提是将不是数组的东西转换为数组,以便排序结果并限制您想要返回的结果。
基本上丑陋!,而且设计非常糟糕。也只是使用 mapReduce,因为它是改变返回文档结构的唯一方法(通过 JavaScript 处理)。该逻辑也可以在客户端执行,唯一的优点是在通过网络连接发送之前剥离不需要的内容。
使用数组对“更新”内容造成一些开销的想法也相当“愚蠢”。 MongoDB 从一开始就支持匹配的位置更新,正确的结构和使用都相当简单:
{
"_id" : ObjectId("2bjbkjb4234j134124"),
"messages" : [
{
"_id": "56a7b13f24236dea1247cdc7",
"authorName" : "Nick",
"timestamp" : 1.453699391078E12,
"message" : "Hello"
},
// etc
]
}
因此,如果您想匹配和更新特定的数组条目(假设到处都是 unqiue,但如果需要,只需调整到“每个文档”)只需在查询部分应用标识符和位置 $ 运算符在语句的“更新”部分:
db.collection.update(
{ "messages._id": "56a7b13f24236dea1247cdc7" },
{ "$set": {
"messages.$.message": "something new",
"messages.$.timestamp": aNewValue
}}
)
使用$push 将项目添加到数组还具有所有“最新”条目默认添加到数组末尾的优点。因此,除非您更改此设置(并且不要修改因此想要最新的时间戳),否则您需要做的就是$slice“已经是一个数组”,而无需进一步处理:
db.collection.find(
{},
{ "messages": { "$slice": -10 } }
)
如果您真的想要修改后的字段(例如“时间戳”)来影响排序,那么您可以简单地使用 $sort 修饰符对$push 进行存储。这甚至可以通过简单的批量操作应用于修改后的数组元素:
var bulk = db.collection.initializeOrderedBulkOp();
// Update the matched element
bulk.find({
"_id": ObjectId("2bjbkjb4234j134124"),
"messages._id": "56a7b13f24236dea1247cdc7"
}).updateOne({
"$set": {
"messages.$.message": "something new",
"messages.$.timestamp": aNewValue
}
});
// Sort the array on timestamp
bulk.find({
"_id": ObjectId("2bjbkjb4234j134124"),
"messages._id": "56a7b13f24236dea1247cdc7"
}).updateOne({
"$push": { "messages": { "$each": [], "$sort": { "timestamp": 1 } } }
})
// Send and receive from server
bulk.execute();
虽然这实际上是两个更新语句(因为您不能在单个更新操作中使用两个运算符语句修改相同的文档路径),但它仍然可以作为对服务器的单个请求和响应,因此很漂亮高效。
当然,如果您不想永久存储订单,那么至少可以在聚合框架中操作数组,其方式通常比通过 mapReduce 的 JavaScript 处理更有效:
db.collection.aggregate([
{ "$match": ObjectId("2bjbkjb4234j134124") },
{ "$unwind": "$messages" },
{ "$sort": { "messages.timestamp": -1 } }, // in reverse order with $limit
{ "$limit": 10 },
{ "$group": {
"_id": "$_id",
"messages": { "$push": "$messages" }
}}
])
甚至超级喜欢使用新的 MongoDB 3.2 运算符处理多个文档:
db.collection.aggregate([
{ "$unwind": "$messages" },
{ "$sort": { "_id": 1, "messages.timestamp": 1 } },
{ "$group": {
"_id": "$_id",
"messages": { "$push": "$messages" }
}},
{ "$project": {
"messages": { "$slice": [ "$messages", -10 ] }
}}
])
但在所有情况下,最有效的考虑因素是数据应该:
是一个“数组”,而不是嵌套在对象的命名键下
理想情况下按最常见用例的顺序存储,以便在阅读时访问。
最后要真正关注的是,如果你“真的”打算在一个数组甚至一个文档中存储 50,000 条消息(因为在 StackOverflow 上提问时没有人会过分夸大其词),那么这些总是最好存在于他们自己的集合中,即使没有超过 BSON 文档限制(最有可能的事件是超过了),性能考虑确实很糟糕。
考虑数据的使用模式应该是这里的主要目标。因此,仅仅因为您“可以”将引用的文档存储在另一个文档中,除非您有一个用例在一个请求中需要“所有这些”(绝对不会 50,000),那么您不应该这样做。