在MongoDB中,findAndModify和update有什么区别?

194

我对MongoDB中的findAndModify方法有些困惑。相比于update方法,它有什么优势?在我看来,它只是先返回项目,然后再更新它。但是,我为什么需要先返回该项目呢?我读过MongoDB权威指南,它说这个方法很好用,可以方便地操作队列和执行其他需要原子性的取值和赋值操作。但我不明白它是如何实现这一点的。有人能给我解释一下吗?

6个回答

178

如果您获取一个项并更新它,那么在这两个步骤之间可能会有另一个线程进行更新。如果您先更新一个项然后再获取它,可能会有另一个更新在其中,并且您将收到与您更新的不同的项。

“原子方式”执行意味着您保证获得的是您正在更新的完全相同的项-即没有其他操作可以在其中发生。


8
我还有点困惑。findAndModify 如何保证没有其他更新操作干扰它? - chaonextdoor
87
@chaonextdoor 的 findAndModify 在开始操作时会获取数据库的锁定,这样在运行时就不会有其他操作可以处理。当它完成操作后,会释放锁定。 - Lycha
4
findAndModify指令只有在修改数据请求时才会获取锁,因此多个进程可以同时更新同一条记录。 - Mark Unsworth
6
如果在使用 findAndModify 时存在锁定方面的 bug,你可以与 10gen 开一个支持案例 - 我可以保证工程师们会尽快修复它。不过,如果真的出现这种情况,我们应该会看到很多人报告这种行为问题,但实际上几乎所有使用它的人都能正常工作 - 出现似乎无法工作的情况通常是因为客户端逻辑或实现错误,但是当然,在复杂的软件中总会有 bug。 - Asya Kamsky
5
@AsyaKamsky,我本来想和你争论一下,但后来意识到你是正确的,所以现在我考虑道歉。抱歉! - funkyeah
显示剩余11条评论

60

findAndModify返回文档,而update不会。

如果我正确理解了mongoDB的原始作者之一Dwight Merriman的话,使用update修改单个文档即("multi":false}也是原子操作。目前,使用update进行等价的更新应该比使用findAndModify更快。


36

来自MongoDB文档(重点加粗):

  • 默认情况下,这两种操作仅修改单个文档。但是,带有multi选项的update()方法可以修改多个文档

  • 如果多个文档符合更新条件,则对于findAndModify(),您可以指定排序以在一定程度上控制要更新的文档。 使用update()方法的默认行为时,在多个匹配文档时无法指定要更新的单个文档。

  • 默认情况下,findAndModify()方法返回文档的修改前版本。要获取更新后的文档,请使用new选项。 update()方法返回包含操作状态的WriteResult对象。要返回更新后的文档,请使用find()方法。但是,其他更新可能已在您的更新和文档检索之间修改了该文档。此外,如果更新仅修改了一个文档,但多个文档匹配,则需要使用其他逻辑来标识更新的文档。

  • 在MongoDB 3.2之前,您不能指定写关注findAndModify()以覆盖默认写关注,而您可以在MongoDB 2.6后的update()方法中指定写关注。

修改单个文档时,findAndModify()和update()方法都会以原子方式更新该文档。


1
你可以始终指定写入关注度来更新操作。此外,自3.2版本(技术上是3.1.1)以来,您也可以指定写入关注度来执行findAndModify操作。https://jira.mongodb.org/browse/SERVER-6558 - Asya Kamsky
我发现在MongoDB 3.6中,尽管文档说明findAndModify()默认只修改一个文档,而update()可以更新一个或多个文档,但当我使用arrayFilters时,findAndModify()会更新所有匹配项。也许这是一个bug? - WesternGun
没有错误 - arrayFilters 允许您更新多个数组元素,但它们仍然在单个文档中。 - Asya Kamsky

10

一个有用的用例类别是计数器和类似情况。例如,看一下这段代码(MongoDB测试之一):find_and_modify4.js

因此,使用 findAndModify 一步完成计数器递增并获取递增后的值。相比之下:如果您(A)在两个步骤中执行此操作,而其他人(B)在您的步骤之间执行相同的操作,则 A 和 B 可能会获得相同的最后一个计数器值,而不是两个不同的值(这只是可能出现问题的一个例子)。


7
这是一个古老但重要的问题,其他答案引导我提出了更多的问题,直到我意识到:这两种方法非常相似,在许多情况下您可以使用任何一种。
  • findAndModifyupdate 都执行单个请求内的原子更改,例如增加计数器;实际上,<query><update> 参数在很大程度上是相同的
  • 使用两者,当服务器找到匹配查询的文档时,原子更改直接在该文档上进行,即在服务器确认查询有效并应用更新的一小部分毫秒内在该文档上进行内部写锁定

没有系统级别的写锁或信号量可以由用户获得。 MongoDB故意不让用户轻松地检出文档,然后更改它,然后将其写回,同时防止其他人在此期间更改该文档。(虽然开发人员可能认为他们想要那样做,但从可扩展性和并发性的角度来看,这通常是反模式... 作为一个简单的例子,想象一下客户端获取写锁,然后在持有锁期间被杀死。如果您真的需要写锁,则可以在文档中创建一个并使用原子更改来比较和设置它,然后确定自己的恢复过程以处理遗弃的锁等。但是,如果您走这条路,请小心。)

据我所知,这些方法的两个主要区别为:

  • 如果希望在更新时获得文档副本:只有 findAndModify 允许此操作,默认返回原始记录,或者可以返回更新后的new 记录;而使用 update 只能得到 WriteResult,而在读取或更新之前立即读取文档无法防止其他进程同时更改记录
  • 如果可能有多个匹配的文档findAndModify 仅更改其中一个,并允许您自定义sort以指示应更改哪一个;update 可以更改所有的记录(默认情况下只更改一个),但不能告诉您要更改哪一条

因此,在 HungryCoder 所说的情况下,update 更有效率,如果您可以接受其限制(例如,您不需要读取文档;或者当然,如果您正在更改多个记录)。但是对于许多原子更新,您确实需要文档,并且在这种情况下就需要 findAndModify


感谢您的解释。非常清晰明了,直截了当。 - Joey

-1
我们在计数器操作(增加或减少)和其他单字段变异情况下使用了findAndModify()。将我们的应用程序从Couchbase迁移到MongoDB时,我发现这个API可以替换代码,该代码执行GetAndlock(),在本地修改内容,replace()以保存并再次Get()以获取更新后的文档。使用mongoDB,我只使用了这个单一的API,它返回更新后的文档。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接