处理 MongoDB 中的可选/空数据

Posted

技术标签:

【中文标题】处理 MongoDB 中的可选/空数据【英文标题】:Handling optional/empty data in MongoDB 【发布时间】:2013-05-09 08:44:33 【问题描述】:

我记得在某处读到过,当文档的整个结构已经就位以备更新时,mongo 引擎会更加舒适,所以这是问题所在。

在处理“空”数据时,例如插入空字符串时,我应该将其默认为null"",还是根本不插入?


    _id: ObjectId("5192b6072fda974610000005"),
    description: ""


    _id: ObjectId("5192b6072fda974610000005"),
    description: null


    _id: ObjectId("5192b6072fda974610000005")

您必须记住,description 字段可能会或可能不会在每个文档中填写(基于用户输入)。

【问题讨论】:

Storing null vs not storing the key at all in MongoDB的可能重复 【参考方案1】:

简介

如果文档没有值,则数据库认为其值为null。假设一个数据库包含以下文档:

 "_id" : ObjectId("5192d23b1698aa96f0690d96"), "a" : 1, "desc" : "" 
 "_id" : ObjectId("5192d23f1698aa96f0690d97"), "a" : 1, "desc" : null 
 "_id" : ObjectId("5192d2441698aa96f0690d98"), "a" : 1 

如果您创建一个查询来查找字段 desc 不同于 null 的文档,您将只得到一个文档:

db.test.find(desc: $ne: null)
// Output:
 "_id" : ObjectId("5192d23b1698aa96f0690d96"), "a" : 1, "desc" : "" 

数据库不会区分没有 desc 字段的文档和具有 desc 字段且值为 null 的文档。再来一项测试:

db.test.find(desc: null)
// Output:
 "_id" : ObjectId("5192d2441698aa96f0690d98"), "a" : 1 
 "_id" : ObjectId("5192d23f1698aa96f0690d97"), "a" : 1, "desc" : null 

但差异仅在查询中被忽略,因为如上面最后一个示例所示,字段仍保存在磁盘上,您将收到与发送到 MongoDB 的文档具有相同结构的文档。

问题

在处理“空”数据时,例如插入一个空字符串时,我应该将其默认为null,“”还是根本不插入?

desc: null 没有太大区别,因为大多数operators 都会有相同的结果。您应该只特别注意这两个运算符:

$exists $type

我会保存不带 desc 字段的文档,因为运算符将继续按预期工作,并且我会节省一些空间。

填充因子

如果您知道数据库中的文档经常增长,那么 MongoDB 可能需要在更新期间移动文档,因为之前的文档位置没有足够的空间。为了防止移动文档,MongoDB 为每个文档分配额外的空间。

MongoDB 为每个文档分配的额外空间量由padding factor 控制。您不能(也不需要)选择填充因子,因为 MongoDB 会自适应地学习它,但是您可以通过使用 null 值填充可能的未来字段来帮助 MongoDB 为每个文档预分配内部空间。差异非常小(取决于您的应用程序),在 MongoDB 学习最佳填充因子后可能会更小。

稀疏索引

本部分对您目前的具体问题不太重要,但在您遇到类似问题时可能会对您有所帮助。

如果您在字段 desc 上创建 unique index,那么您将无法保存多个具有相同值的文档,并且在之前的数据库中,我们有多个文档在字段 desc 上具有相同的值。让我们尝试在前面介绍的数据库中创建一个唯一索引,看看我们得到了什么错误:

db.test.ensureIndex(desc: 1, unique: true)
// Output:

    "err" : "E11000 duplicate key error index: test.test.$desc_1  dup key:  : null ",
    "code" : 11000,
    "n" : 0,
    "connectionId" : 3,
    "ok" : 1

如果我们希望能够在某个字段上创建唯一索引让某些文档将此字段留空,我们应该创建一个sparse index。让我们再次尝试创建唯一索引:

// No errors this time:
db.test.ensureIndex(desc: 1, unique: true, sparse: true)

到目前为止,一切都很好,但我为什么要解释这一切?因为稀疏索引有一种晦涩的行为。在以下查询中,我们希望有 ALL 文档按 desc 排序。

db.test.find().sort(desc: 1)
// Output:
 "_id" : ObjectId("5192d23f1698aa96f0690d97"), "a" : 1, "desc" : null 
 "_id" : ObjectId("5192d23b1698aa96f0690d96"), "a" : 1, "desc" : "" 

结果看起来很奇怪。丢失的文件怎么了?让我们试试不排序的查询:

 "_id" : ObjectId("5192d23b1698aa96f0690d96"), "a" : 1, "desc" : "" 
 "_id" : ObjectId("5192d23f1698aa96f0690d97"), "a" : 1, "desc" : null 
 "_id" : ObjectId("5192d2441698aa96f0690d98"), "a" : 1 

本次所有文件均已退回。发生了什么?这很简单,但不是那么明显。当我们按desc对结果进行排序时,我们使用之前创建的稀疏索引,没有desc的文档没有条目场地。以下查询向我们展示了使用索引对结果进行排序

db.test.find().sort(desc: 1).explain().cursor
// Output:
"BtreeCursor desc_1"

我们可以使用 hint 跳过索引:

db.test.find().sort(desc: 1).hint($natural: 1)
// Output:
 "_id" : ObjectId("5192d23f1698aa96f0690d97"), "a" : 1, "desc" : null 
 "_id" : ObjectId("5192d2441698aa96f0690d98"), "a" : 1 
 "_id" : ObjectId("5192d23b1698aa96f0690d96"), "a" : 1, "desc" : "" 

总结

如果包含 desc: null,稀疏唯一索引将不起作用 如果包含desc: "",稀疏唯一索引将不起作用 稀疏索引可能会改变查询结果

【讨论】:

@asya-kamsky:如果 OP 了解选择之间的差异,他将能够为他确定最佳解决方案。 这些都与经常在前面创建可能稍后填写的字段(以防止文档增长)的原因有关。但是创建空字符串还不够好,因为如果设置了该字段,以后需要分配更多空间。 @asya-kamsky:我没有考虑预分配的数据文件,因为您可以(并且应该)通过设置padding factor来解决这个问题 您不能设置填充因子。这不是预分配的 数据文件 - 我说的是为最终的文档大小预分配足够的空间。 @asya-kamsky:谢谢你让我知道(我认为可以设置填充因子)。然后我们有一个性能内存权衡,MongoDB 将自适应地学习。一个问题:你可能比我更了解MongoDB,那你为什么不回答这个问题?【参考方案2】:

空值字段没有该字段的文档之间几乎没有区别。主要区别在于前者消耗的磁盘空间很小,而后者则完全不消耗。可以使用$exists操作符来区分。

带有空字符串的字段与它们完全不同。虽然这取决于目的,但我不建议将其用作null 的替代品。准确地说,它们应该用来表示不同的事物。例如,考虑投票。投空白票的人和不被允许投票的人是不同的。前一票是空字符串,后一票是null

已经有a similar question here。

【讨论】:

以上是关于处理 MongoDB 中的可选/空数据的主要内容,如果未能解决你的问题,请参考以下文章

AV音频播放器。音频文件的可选重定向。在 _audio 我得到空

引用一个空的可选值

PHP函数中的可选参数不考虑顺序

TypeScript 中的可选参数可以为空吗?

实体框架核心 - 无法删除可选的可为空关系

ES11中的可选链等语法