第二章 – 更新
最后更新于:2022-04-01 00:54:55
在第一章,我们介绍了 CRUD 的四分之三(create, read, update 和 delete) 操作。这章,我们来专门来讨论我们跳过的那个操作: `update`。 `Update` 有些独特的行为,这是为什么我们把它独立成章。
## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#update-覆盖还是-set)Update: 覆盖还是 $set
最简单的情况, `update` 有两个参数: 选择器 (`where`) 和需要更新字段的内容。假设 Roooooodles 长胖了,你会希望我们这样操作:
~~~
db.unicorns.update({name: 'Roooooodles'},
{weight: 590})
~~~
(如果你已经把 `unicorns` 集合玩坏了,它已经不是原来的数据了的话,再执行一次 `remove` 删除所有数据,然后重新插入第一章中所有的代码。)
现在,如果你查一下被更新了的记录:
~~~
db.unicorns.find({name: 'Roooooodles'})
~~~
你会发现 `update` 的第一个惊喜,没找到任何文档。因为我们指定的第二个参数没有使用任何的更新选项,因此,它**replace** 了原始文档。也就是说, `update` 先根据 `name` 找到一个文档,然后用新文档(第二个参数)覆盖替换了整个文档。这和 SQL 的 `update` 命令的完全不一样。在某些情况下,这非常理想,可以用于某些完全动态更新上。但是,如果你只希望改变一个或者几个字段的值的时候,你应该用 MongoDB 的 `$set` 操作。继续,让我们来更新重置这个丢失的数据:
~~~
db.unicorns.update({weight: 590}, {$set: {
name: 'Roooooodles',
dob: new Date(1979, 7, 18, 18, 44),
loves: ['apple'],
gender: 'm',
vampires: 99}})
~~~
这里不会覆盖新字段 `weight` 因为我们没有指定它。现在让我们来执行:
~~~
db.unicorns.find({name: 'Roooooodles'})
~~~
我们拿到了期待的结果。因此,在最开始的时候,我们正确的更新 weight 的方式应该是:
~~~
db.unicorns.update({name: 'Roooooodles'},
{$set: {weight: 590}})
~~~
## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#update-操作符)Update 操作符
除了 `$set`,我们还可以用其他的更新操作符做些有意思的事情。所有的更新操作都是对字段起作用 - 所以你不用担心整个文档被删掉。比如,`$inc` 可以用来给一个字段增加一个正/负值。假设说 Pilot 获得了非法的两个 vampire kills 点,我们可以这样修正它:
~~~
db.unicorns.update({name: 'Pilot'},
{$inc: {vampires: -2}})
~~~
假设 Aurora 忽然长牙了,我们可以给她的 `loves` 字段加一个值,通过 `$push` 操作:
~~~
db.unicorns.update({name: 'Aurora'},
{$push: {loves: 'sugar'}})
~~~
MongoDB 手册的 [Update Operators](http://docs.mongodb.org/manual/reference/operator/update/#update-operators) 这章,可以查到更多可用的更新操作符的信息。
## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#upserts)Upserts
用 `update` 还有一个最大的惊喜,就是它完全支持 `upserts`。所谓 `upsert` 更新,即在文档中找到匹配值时更新它,无匹配时向文档插入新值,你可以这样理解。要使用 upsert 我们需要向 update 写入第三个参数 `{upsert:true}`。
一个最常见的例子是网站点击计数器。如果我们想保存一个实时点击总数,我们得先看看是否在页面上已经有点击记录,然后基于此再决定执行更新或者插入操作。如果省略 upsert 选项(或者设为 false),执行下面的操作不会带来任何变化:
~~~
db.hits.update({page: 'unicorns'},
{$inc: {hits: 1}});
db.hits.find();
~~~
但是,如果我们加上 upsert 选项,结果会大不同:
~~~
db.hits.update({page: 'unicorns'},
{$inc: {hits: 1}}, {upsert:true});
db.hits.find();
~~~
由于没有找到字段 `page` 值为 `unicorns`的文档,一个新的文档被生成插入。当我们第二次执行这句命令的时候,这个既存的文档将会被更新,且 `hits` 会被增加到 2。
~~~
db.hits.update({page: 'unicorns'},
{$inc: {hits: 1}}, {upsert:true});
db.hits.find();
~~~
## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#批量-updates)批量 Updates
关于 `update` 的最后一个惊喜,默认的,它只更新单个文档。到目前为止,我们的所有例子,看起来都挺符合逻辑的。但是,如果你执行一些像这样的操作的时候:
~~~
db.unicorns.update({},
{$set: {vaccinated: true }});
db.unicorns.find({vaccinated: true});
~~~
你肯定会希望,你所有的宝贝独角兽都被接种疫苗了。为了达到这个目的, `multi` 选项需要设为 true:
~~~
db.unicorns.update({},
{$set: {vaccinated: true }},
{multi:true});
db.unicorns.find({vaccinated: true});
~~~
## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#小结-1)小结
本章中我们介绍了集合的基本 CRUD 操作。我们详细讲解了 `update` 及它的三个有趣的行为。 首先,如果你传 MongoDB 一个文档但是不带更新操作, MongoDB 的 `update` 会默认替换现有文档。因此,你通常要用到 `$set` 操作 (或者其他各种可用的用于修改文档的操作)。 其次, `update` 支持 `upsert` 操作,当你不知道文档是否存在的时候,非常有用。 最后,默认情况下, `update` 只更新第一个匹配文档,因此当你希望更新所有匹配文档时,你要用 `multi` 。