【问题标题】:Time Complexity of $addToset vs $push when element does not exist in the Array数组中不存在元素时 $addToset 与 $push 的时间复杂度
【发布时间】:2012-09-01 07:15:09
【问题描述】:

鉴于:连接是安全的=True,因此更新的返回将包含更新信息。

假设我有一个看起来像这样的文档:

[{'a': [1]}, {'a': [2]}, {'a': [1,2]}]

我发出:

coll.update({}, {'$addToSet': {'a':1}}, multi=True)

结果是:

{u'connectionId': 28,
 u'err': None,
 u'n': 3,
 u'ok': 1.0,
 u'updatedExisting': True
}

即使来的文档已经具有该值。为了避免这种情况,我可以发出一个命令。

coll.update({'a': {'$ne': 1}}, {'$push': {'a':1}}, multi=True)

$addToSet 与带有 $ne 检查的 $push 的时间复杂度比较是多少?

【问题讨论】:

  • 你所说的“时间复杂度”是指与$push相关的比较所花费的时间?
  • 是的。如果带有 $ne 的 $push 将遍历每个元素,我假设 $addToSet 也会这样做。两者中哪一个最适合使用?
  • $push 很容易,因为即使 $push 必须拉出数组(子文档),它也不必比较设置。

标签: mongodb


【解决方案1】:

看起来 $addToSet 正在和你的命令做同样的事情:$push with a $ne check。两者都是 O(N)

https://github.com/mongodb/mongo/blob/master/src/mongo/db/ops/update_internal.cpp

如果速度真的很重要,那么为什么不使用哈希:

代替:

{'$addToSet': {'a':1}}
{'$addToSet': {'a':10}}

使用:

{$set: {'a.1': 1}
{$set: {'a.10': 1}

【讨论】:

  • 嗯,我说 addtoset 是 O(n) 但我被否决了......一定是错的
【解决方案2】:

编辑

好的,因为我一直都错误地阅读了您的问题,事实证明您实际上正在查看两个不同的查询并判断它们之间的时间复杂度。

第一个查询是:

coll.update({}, {'$addToSet': {'a':1}}, multi=True)

第二个是:

coll.update({'a': {'$ne': 1}}, {'$push': {'a':1}}, multi=True)

第一个问题出现在这里,没有索引。 $addToSet,作为更新修饰符,我不相信它使用索引,因为您正在执行全表扫描来完成您需要的操作。

实际上,您正在寻找在a 中没有1 的所有文档,并将$push 的值1 查找到该a 数组。

所以 2 甚至在我们进入时间复杂度之前就指向了第二个查询,因为第一个查询:

  • 不使用索引
  • 将是全表扫描
  • 然后会对$addToSet 进行全阵列扫描(无索引)

所以我在这里几乎已经下定决心,第二个查询是您在使用任何大 O 表示法之前要查找的内容。

这里用大O符号来解释每个查询的时间复杂度是有问题的:

  • 我不确定您想要什么视角,是针对每个文档还是针对整个集合。
  • 我不确定索引本身。使用索引实际上会在 a 上创建一个 Log 算法,但不使用索引不会。

但是第一个查询看起来像:每个文档 O(n),因为:

  • $addToSet 需要遍历每个元素
  • 如果集合不存在,$addToSet 将需要执行 O(1) 操作来插入该集合。我应该注意我不确定 O(1) 是否被取消(轻微阅读表明我的版本),我在这里取消了它。

每个集合,如果没有索引,它将是:O(2n2),因为迭代 a 的复杂性将随着每个新文档呈指数级增加。

没有索引的第二个查询看起来像: O(2n2) (O(n) per document) 我相信因为$ne 会遇到与没有索引的$addToSet 相同的问题。但是对于索引,我相信这实际上是 O(log n log n) (O(log n) per document),因为它会首先找到所有带有 a 的文档,然后在他们的集合中找到所有没有 1 的文档b-树。

因此,根据时间复杂度和开头的注释,我会说查询 2 更好。

老实说,我不习惯用“Big O”表示法解释,所以这是实验性的。

希望对你有帮助,

【讨论】:

  • 你的最后一行是我的问题。它的 ($addToSet) 与 ($push + $ne)。它们都可以,但是如果我必须按照大 O 来评价它们,每个得分如何?
  • @meson10 你一直在用俚语,我一直不明白,因为我用的俚语和你不一样,什么是“Big O”?另外我确实声明 $push 对于“时间复杂度”来说更快。
  • 在计算机科学中,算法的时间复杂度将算法运行所花费的时间量化为问题输入大小的函数。算法的时间复杂度通常使用大 O 表示法来表示,它抑制了乘法常数和低阶项。当以这种方式表示时,时间复杂度被认为是渐近描述的,即,随着输入大小趋于无穷大。例如,如果一个算法在所有大小为 n 的输入上所需的时间最多为 5n3 + 3n,则渐近时间复杂度为 O(n3) smnr.me/TNAp7n
  • @meson10 好吧,我不习惯用数学语法拐弯抹角,而是习惯于用简单的英语解释这一点,但是,在阅读大 O 符号 50 分钟后,我认为我有能够将我的英语翻译成数学。如果我看到错误,我会编辑。
  • 我认为你误读了我的问题。我没有将 $addToSet 与 $ne 与 $push 进行比较。我的意思是比较 $addToSet 与 ($push + $ne)。注意:请注意,在我执行 $addToSet 时没有 $ne 查找。其次,“数学”语法是表示度量的更标准的方式。示例:A 车比 B 快得多,或者 A 车比 B 快 80 公里/小时 :-)
【解决方案3】:

添加我对 addToSet 和从批量更新 100k 文档推送的区别的观察。

当您进行批量更新时。 addToSet 将单独执行。

例如,

bulkInsert.find({x:y}).upsert().update({"$set":{..},"$push":{ "a":"b" } , "$setOnInsert":  {} })

将首先插入并设置文档。然后它执行 addToSet 查询。

我看到了 10k 之间的明显差异

db.collection_name.count() #gives around 40k 

db.collection_name.count({"a":{$in:["b"]}}) # it gives only around 30k

但是当用 $push 替换 $addToSet 时。两个计数查询返回相同的值。

注意:当您不关心数组中的重复条目时。你可以使用 $push。

【讨论】:

    猜你喜欢
    • 2012-08-28
    • 1970-01-01
    • 2015-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多