MongoDB

Table of Contents

1. MongoDB 简介

MongoDB is a document-oriented NoSQL database.

参考:MongoDB Manual

1.1. MongoDB VS. CouchDB

MongoDB 与 CouchDB 很相似,它们都是文档型存储,数据存储格式都是 JSON 型的。

MongoDB 和 CouchDB 的一个重要区别:CouchDB 是一个 MVCC 的系统,而 MongoDB 是一个 update-in-place 的系统。这二者的区别就是,MongoDB 进行写操作时都是即时完成写操作,写操作成功则数据就写成功了;而 CouchDB 一个支持多版本控制的系统,此类系统通常支持多个节点写,而系统会检测到多个系统的写操作之间的冲突并以一定的算法规则予以解决。

参考:MongoDB与CouchDB 全方位对比

1.2. MongoDB 和 RDBMS 的概念对应关系

MongoDB 和 RDBMS 的概念对应关系及其不同如表 1 所示。

Table 1: MongoDB 和 RDBMS 的概念对应关系及其不同
RDBMS MongoDB Difference
Table Collection In RDBMS, the table contains the columns and rows which are used to store the data whereas, in MongoDB, this same structure is known as a collection. The collection contains documents which in turn contains Fields, which in turn are key-value pairs.
Row Document In RDBMS, the row represents a single, implicitly structured data item in a table. In MongoDB, the data is stored in documents.
Column Field In RDBMS, the column denotes a set of data values. These in MongoDB are known as Fields.
Joins Embedded documents In RDBMS, data is sometimes spread across various tables and in order to show a complete view of all data, a join is sometimes formed across tables to get the data. In MongoDB, the data is normally stored in a single collection, but separated by using Embedded documents. So there is no concept of joins in Mongodb.

1.3. 启动服务器

下面命令可启动 MongoDB 服务器:

$ mongod --config /usr/local/etc/mongod.conf
$ mongod --config --fork /usr/local/etc/mongod.conf   # --fork 表示以 daemon 形式启动

MongoDB 服务器默认监听在端口 27017,可以通过 --port 参数修改监听端口。

下面是配置文件 /usr/local/etc/mongod.conf 的一个样例:

$ cat /usr/local/etc/mongod.conf
systemLog:
  destination: file
  path: /usr/local/var/log/mongodb/mongo.log
  logAppend: true
storage:
  dbPath: /usr/local/var/mongodb
net:
  bindIp: 127.0.0.1

1.4. 启动客户端

直接运行 mongo 可启动客户端,默认连接本机的 27017 端口,执行 help 可以查看在线帮助文档。

$ mongo
MongoDB shell version v4.0.0
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 4.0.0
> help
	db.help()                    help on db methods
	db.mycoll.help()             help on collection methods
	sh.help()                    sharding helpers
	rs.help()                    replica set helpers
	help admin                   administrative help
	help connect                 connecting to a db help
	help keys                    key shortcuts
	help misc                    misc things to know
	help mr                      mapreduce

	show dbs                     show database names
	show collections             show collections in current database
	show users                   show users in current database
	show profile                 show most recent system.profile entries with time >= 1ms
	show logs                    show the accessible logger names
	show log [name]              prints out the last segment of log in memory, 'global' is default
	use <db_name>                set current database
	db.foo.find()                list objects in collection foo
	db.foo.find( { a : 1 } )     list objects in foo where a == 1
	it                           result of the last line evaluated; use to further iterate
	DBQuery.shellBatchSize = x   set default number of items to display on shell
	exit                         quit the mongo shell

可以使用参数 --host--port 来改变连接的服务器地址。如不带参数直接执行 mongo 相当于执行:

mongo --host 127.0.0.1 --port 27017

2. MongoDB 基本操作

2.1. 创建、查看、删除数据库

使用 show dbs 可查看系统中有哪些数据库。如:

> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB

使用 use YOUR_DBNAME 可以切换或创建数据为数据库。如:

> use test1
switched to db test1

执行上面命令后,如果数据库 test1 不存在会创建它,否则切换到数据库 test1。

注:使用 show dbs 不会显示刚刚创建的新数据库,你需要往数据库中插入数据后才会显示。

使用 db 可以查看当前数据库。如:

> db
test1

使用 db.dropDatabase() 可以删除当前数据库。如删除 test1 数据库:

> db.dropDatabase()
{ "dropped" : "test1", "ok" : 1 }

2.2. 创建、查看、删除 collection (table)

可以使用 db.createCollection 来创建 collection。如:

> db.createCollection("people")
{ "ok" : 1 }

一般你不用使用 createCollect 显式地创建 collection,直接插入文档时会自动创建 collection。如:

> db.testabc.insert({"name" : "cig01"})    // 如果testabc是不存在的collection,则会先创建它
WriteResult({ "nInserted" : 1 })

使用 show collections (或者 show tables )可以显示当前数据库下有哪些 collection。如:

> show collections
people
testabc

使用 db.testabc.drop() 可以删除集合 testabc。如:

> db.testabc.drop()
true

2.3. 文档基本操作

2.3.1. 插入文档

使用 db.COLLECTION_NAME.insert() 可以往当前数据库中集合 COLLECTION_NAME 下插入一个或多个文档。如插入一个文档:

> db.products.insert( { item: "card", qty: 15 } )
WriteResult({ "nInserted" : 1 })

如果插入的文档数据中没有 _id 字段,则会自动生成一个 _id 字段;如果插入的文档中带有 _id 字段,则用户自己需要确保 _id 字段不重复。

下面是一次插入多个文档的例子:

> db.products.insert([ { item: "pencil", qty: 15 }, { item: "book", qty: 10} ] )
BulkWriteResult({
	"writeErrors" : [ ],
	"writeConcernErrors" : [ ],
	"nInserted" : 2,
	"nUpserted" : 0,
	"nMatched" : 0,
	"nModified" : 0,
	"nRemoved" : 0,
	"upserted" : [ ]
})

使用 db.COLLECTION_NAME.insertOne()db.COLLECTION_NAME.insertMany() 可以往集合中分别插入一个和多个文档,执行成功后它们的返回数据类型和 db.COLLECTION_NAME.insert() 不一样。下面是使用 insertMany() 插入多个文档的实例:

>  db.products.insertMany([ { item: "pencil", qty: 30 }, { item: "book", qty: 40} ] )
{
	"acknowledged" : true,
	"insertedIds" : [
		ObjectId("5b6e7d9490789f89f897931d"),
		ObjectId("5b6e7d9490789f89f897931e")
	]
}

可见, insertMany() 的返回数据确实和 insert() 不一样。

注:在 nodejs 的 MongoDB 驱动中 insert() 已经被标记为过时的,推荐使用 insertOne()insertMany()

2.3.2. 查询文档

使用 db.COLLECTION_NAME.find() 可以得到集合中的所有文档。如:

> db.products.find()
{ "_id" : ObjectId("5b6e7d6790789f89f897931a"), "item" : "card", "qty" : 15 }
{ "_id" : ObjectId("5b6e7d8c90789f89f897931b"), "item" : "pencil", "qty" : 15 }
{ "_id" : ObjectId("5b6e7d8c90789f89f897931c"), "item" : "book", "qty" : 10 }
{ "_id" : ObjectId("5b6e7d9490789f89f897931d"), "item" : "pencil", "qty" : 30 }
{ "_id" : ObjectId("5b6e7d9490789f89f897931e"), "item" : "book", "qty" : 40 }

通过指定 pretty() 方法可以更好地显示结果。如:

> db.products.find().pretty()
{
	"_id" : ObjectId("5b6e7d6790789f89f897931a"),
	"item" : "card",
	"qty" : 15
}
{
	"_id" : ObjectId("5b6e7d8c90789f89f897931b"),
	"item" : "pencil",
	"qty" : 15
}
{
	"_id" : ObjectId("5b6e7d8c90789f89f897931c"),
	"item" : "book",
	"qty" : 10
}
{
	"_id" : ObjectId("5b6e7d9490789f89f897931d"),
	"item" : "pencil",
	"qty" : 30
}
{
	"_id" : ObjectId("5b6e7d9490789f89f897931e"),
	"item" : "book",
	"qty" : 40
}
2.3.2.1. 按条件查询

通过 find() 的参数指定条件,可以查询指定要求的文档,如表 2 所示。

Table 2: MongoDB 查询条件
操作 格式 MongoDB 范例 RDBMS 类似语句
等于 {<key>:<value>} db.collection.find({"field1":"value1"}) WHERE field1 = 'value1'
小于 {<key>:{$lt:<value>}} db.collection.find({"field2":{$lt:30}}) WHERE field2 < 30
小于或等于 {<key>:{$lte:<value>}} db.collection.find({"field2":{$lte:30}}) WHERE field2 <= 30
大于 {<key>:{$gt:<value>}} db.collection.find({"field2":{$gt:30}}) WHERE field2 > 30
大于或等于 {<key>:{$gte:<value>}} db.collection.find({"field2":{$gte:30}}) WHERE field2 >= 30
不等于 {<key>:{$ne:<value>}} db.collection.find({"field2":{$ne:30}}) WHERE field2 != 30

比如,查询 item 为 book 的文档:

> db.products.find({item: "book"})
{ "_id" : ObjectId("5b6e7d8c90789f89f897931c"), "item" : "book", "qty" : 10 }
{ "_id" : ObjectId("5b6e7d9490789f89f897931e"), "item" : "book", "qty" : 40 }

又如,查询 qty 大于等于 30 的文档:

> db.products.find({qty: {$gte: 30}})
{ "_id" : ObjectId("5b6e7d9490789f89f897931d"), "item" : "pencil", "qty" : 30 }
{ "_id" : ObjectId("5b6e7d9490789f89f897931e"), "item" : "book", "qty" : 40 }
2.3.2.2. 组合查询(AND 和 OR)

find() 方法可以传入多个 key,每个 key 以逗号隔开,即相当于 RDBMS 中的 AND 条件。如查询 item 为 book,并且 qty 大于等于 30 的文档:

> db.products.find({item: "book", qty: {$gte: 30}})                  // AND实例
{ "_id" : ObjectId("5b6e7d9490789f89f897931e"), "item" : "book", "qty" : 40 }

OR 条件语句使用了关键字 $or ,其语法为:

db.collection.find(
   {
      $or: [
         {key1: value1}, {key2:value2}
      ]
   }
)

下面是查询 item 为 book,或者 qty 大于等于 30 的文档:

> db.products.find({$or: [{item: "book"}, {qty: {$gte: 30}} ] })     // OR实例
{ "_id" : ObjectId("5b6e7d8c90789f89f897931c"), "item" : "book", "qty" : 10 }
{ "_id" : ObjectId("5b6e7d9490789f89f897931d"), "item" : "pencil", "qty" : 30 }
{ "_id" : ObjectId("5b6e7d9490789f89f897931e"), "item" : "book", "qty" : 40 }
2.3.2.3. 过滤某些字段

查询文档时,默认会返回所有字段。

我们可以通过 db.collection.find(query, projection) 的第二个参数 projection 来指定返回或不返回哪些字段。它有两种使用方式。

方式一(称为 inclusion 模式):设置需要返回的字段为 1 或者 true,那么其它字段都不会返回。

> db.products.find()
{ "_id" : ObjectId("5b6e7d6790789f89f897931a"), "item" : "card", "qty" : 15 }
{ "_id" : ObjectId("5b6e7d8c90789f89f897931b"), "item" : "pencil", "qty" : 15 }
{ "_id" : ObjectId("5b6e7d8c90789f89f897931c"), "item" : "book", "qty" : 10 }
{ "_id" : ObjectId("5b6e7d9490789f89f897931d"), "item" : "pencil", "qty" : 30 }
{ "_id" : ObjectId("5b6e7d9490789f89f897931e"), "item" : "book", "qty" : 40 }
> db.products.find({}, {_id: 1, item: 1} )    // inclusion模式
{ "_id" : ObjectId("5b6e7d6790789f89f897931a"), "item" : "card" }
{ "_id" : ObjectId("5b6e7d8c90789f89f897931b"), "item" : "pencil" }
{ "_id" : ObjectId("5b6e7d8c90789f89f897931c"), "item" : "book" }
{ "_id" : ObjectId("5b6e7d9490789f89f897931d"), "item" : "pencil" }
{ "_id" : ObjectId("5b6e7d9490789f89f897931e"), "item" : "book" }

方式二(称为 exclusion 模式):设置不需要返回的字段为 0 或者 false,那么其它字段都会返回。

> db.products.find({}, {qty: 0})               // exclusion模式
{ "_id" : ObjectId("5b6e7d6790789f89f897931a"), "item" : "card" }
{ "_id" : ObjectId("5b6e7d8c90789f89f897931b"), "item" : "pencil" }
{ "_id" : ObjectId("5b6e7d8c90789f89f897931c"), "item" : "book" }
{ "_id" : ObjectId("5b6e7d9490789f89f897931d"), "item" : "pencil" }
{ "_id" : ObjectId("5b6e7d9490789f89f897931e"), "item" : "book" }

注:在设置 projection 参数时,我们要么把想要返回的字段都指定为 1(或 true);要么把不想要返回的字段都指定为 0(或 false);我们不能指定部分字段为 0,部分字段为 1。

2.3.3. 更新文档

使用 db.collection.update()db.collection.save() 可以使用更新文档。

update() 的用法如下:

db.collection.update(
   <query>,
   <update>,
   {
     upsert: <boolean>,
     multi: <boolean>,   // 默认只更新找到的第一个文档,如果这个参数为true,则更新所有文档
     writeConcern: <document>,  // 设置抛出异常的级别
     collation: <document>,
     arrayFilters: [ <filterdocument1>, ... ]
   }
)

其中:query 用来指定查询条件,而 update 指定更新的对象和一些更新操作符(如 $set , $inc 等,可参考 https://docs.mongodb.com/manual/reference/operator/update/ )。

例如,更新 item 为 book 的文档,把其 qty 字段设置为 100:

> db.products.update({item: "book"}, {$set : {qty: 100}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.products.find()
{ "_id" : ObjectId("5b6e7d6790789f89f897931a"), "item" : "card", "qty" : 15 }
{ "_id" : ObjectId("5b6e7d8c90789f89f897931b"), "item" : "pencil", "qty" : 15 }
{ "_id" : ObjectId("5b6e7d8c90789f89f897931c"), "item" : "book", "qty" : 100 }
{ "_id" : ObjectId("5b6e7d9490789f89f897931d"), "item" : "pencil", "qty" : 30 }
{ "_id" : ObjectId("5b6e7d9490789f89f897931e"), "item" : "book", "qty" : 40 }

上面例子中,只会更新第一个找到的文档,要更新所有找到的文档,则需要指定 multitrue ,如:

> db.products.update({item: "book"}, {$set : {qty: 100}}, {multi:true})  // 所有book的qty设置为100
WriteResult({ "nMatched" : 2, "nUpserted" : 0, "nModified" : 1 })
> db.products.update({item: "book"}, {$inc : {qty: 1}}, {multi:true})    // 所有book的qty增加1
WriteResult({ "nMatched" : 2, "nUpserted" : 0, "nModified" : 2 })

save() 方法通过传入的文档来替换已有文档。不指定 _id 字段 save() 方法类似于 insert() 方法;如果指定 _id 字段,则会更新该 _id 的数据。例如,替换 _id 为 5b6e7d9490789f89f897931e 的文档:

> db.products.save({ "_id" : ObjectId("5b6e7d9490789f89f897931e"), "item" : "book1", "qty" : 100 })
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

2.3.4. 删除文档

使用 db.collection.remove() 可以删除指定文档,用法如下:

db.collection.remove(
   <query>,
   {
     justOne: <boolean>,      // 是否只删除第一个匹配文档,默认为false
     writeConcern: <document>,
     collation: <document>
   }
)

下面是删除 item 为 pencil 的文档的实例:

> db.products.find()
{ "_id" : ObjectId("5b6e7d6790789f89f897931a"), "item" : "card", "qty" : 15 }
{ "_id" : ObjectId("5b6e7d8c90789f89f897931b"), "item" : "pencil", "qty" : 15 }
{ "_id" : ObjectId("5b6e7d8c90789f89f897931c"), "item" : "book", "qty" : 101 }
{ "_id" : ObjectId("5b6e7d9490789f89f897931d"), "item" : "pencil", "qty" : 30 }
{ "_id" : ObjectId("5b6e7d9490789f89f897931e"), "item" : "book1", "qty" : 100 }
> db.products.remove({item: "pencil"})      // 删除item为pencil的文档
WriteResult({ "nRemoved" : 2 })
> db.products.find()
{ "_id" : ObjectId("5b6e7d6790789f89f897931a"), "item" : "card", "qty" : 15 }
{ "_id" : ObjectId("5b6e7d8c90789f89f897931c"), "item" : "book", "qty" : 101 }
{ "_id" : ObjectId("5b6e7d9490789f89f897931e"), "item" : "book1", "qty" : 100 }

在执行 remove() 命令前先执行 find() 命令来判断执行的条件是否正确,以避免误删数据。

2.4. 数据备份(mongodump/mongorestore)

使用工具 mongodump 和 mongorestore 可以备份和恢复数据库。

备份 mongodb 数据库:

$ mongodump -h <hostname>:<port> -u <user> -p <password> -o <dbdirectory>

上面命令会备份所有的数据库,如果只想备份某个数据库,则可以通过参数 -d <yourdbname> 来指定。备份完成后,会保存备份文件到 -o 指定的目录中,下面是备份目录中备份文件的一个例子:

$ find output
output/
output/db1/tbl1.bson
output/db1/tbl1.metadata.json
output/db1/tbl2.bson
output/db1/tbl2.metadata.json

恢复 mongodb:

$ mongorestore -h <hostname>:<port> -u <user> -p <password> <dbdirectory>

如果我们想把备份文件压缩为一个文件,则可以指定参数 --gzip --archive=file.gz ,如:

$ mongodump -h <hostname>:<port> -u <user> -p <password> --db <yourdbname> --gzip --archive=/path/to/archive.gz      # 备份
$ mongorestore -h <hostname>:<port> -u <user> -p <password> --gzip --archive=/path/to/archive.gz                     # 恢复

3. MongoDB 集群操作

MongoDB 的集群搭建方式主要有三种:主从模式,Replica Set 模式(副本集模式),Sharding 模式。 其中,主从模式已经被副本集取代,目前副本集模式应用较为广泛,Sharding 模式提供了 MongoDB 的“水平扩展”能力,但配置维护较为复杂。

3.1. Replica Set

A replica set in MongoDB is a group of mongod processes that maintain the same data set. Replica sets provide redundancy and high availability.

一个副本集只能有一个主节点(Primary),可以有多个从节点(Secondaries),还可以有一个仲裁节点(当参与选举的节点无法选出主节点时仲裁节点充当仲裁的作用,仲裁节点不存储数据)。 主节点可以读写,而从节点不能写只能读。 主节点将数据修改操作记录在 operation log(简称 oplog)中,oplog 会自动复制到从节点中,从节点通过应用 oplog 到自己数据集来实现数据的同步。一般配置奇数个节点,至少三个节点,如图 1 所示。

mongodb_replica_set.svg

Figure 1: MongoDB 的 Replica Set 模式

如果主节点宕机后会进行自动选举,选举出一个从节点为新的主节点,从而实现自动故障转移。

参考:
https://docs.mongodb.com/manual/replication/
https://docs.mongodb.com/manual/tutorial/deploy-replica-set/

3.1.1. 第一步、在每个机器上启动 MongoDB

后文将介绍如果部署 3 个节点的 MongoDB 副本集集群,假设 3 个节点的域名分别为表 3 所示。

Table 3: 3 个节点的域名
Replica Set Member Hostname
Member 0 mongodb0.example.net
Member 1 mongodb1.example.net
Member 2 mongodb2.example.net

在每个机器上启动 MongoDB 很简单,分别登录这 3 个机器,带 --replSet <name> 参数启动 mongod 即可:

$ mongod --replSet "rs0" --fork --config <path-to-config>

说明:这和启动普通的 mongod 节点没有什么区别,唯一的不同在于需要设置副本集的名字(这个例子中为 rs0)。注: 同一副本集群的 replSet 名称必需相同。 除了在命令行直接指定外,也可以在其配置文件中指定副本集的名字,如:

replication:
   replSetName: "rs0"

3.1.2. 第二步、在某一台机制上执行 rs.initiate 命令

登录 3 台机器中的任意一台(比如 mongodb0.example.net),进入到 mongo shell 中,执行 rs.initiate 命令,在这个命令的参数中指定副本集名字和其它节点的信息,比如:

rs.initiate( {
   _id : "rs0",
   members: [
      { _id: 0, host: "mongodb0.example.net:27017" },
      { _id: 1, host: "mongodb1.example.net:27017" },
      { _id: 2, host: "mongodb2.example.net:27017" }
   ]
})

3.1.3. 第三步、查看各个节点状态

通过前面的步骤,集群的基本配置已经完成了。在 mongo shell 中执行 rs.status() 可以查看各个节点状态。如:

......
	"members" : [
		{
			"_id" : 0,
			"name" : "mongodb0.example.net:27017",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			"uptime" : 1125,
......
        {
			"_id" : 1,
			"name" : "mongodb1.example.net:27018",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY",
			"uptime" : 735,
......
		{
			"_id" : 2,
			"name" : "mongodb2.example.net:27019",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY",
			"uptime" : 735,

从输出中可知,mongodb0.example.net 是主节点,其它两个是从节点。

3.1.4. 集群的其它操作

MongoDB 还支持其它一些操作,如 rs.stepDown():将主节点降级为从节点;rs.add()增加节点;rs.remove()删除节点,等等。可以执行 rs.help() 查看帮助,如:

> rs.help()
	rs.status()                                { replSetGetStatus : 1 } checks repl set status
	rs.initiate()                              { replSetInitiate : null } initiates set with default settings
	rs.initiate(cfg)                           { replSetInitiate : cfg } initiates set with configuration cfg
	rs.conf()                                  get the current configuration object from local.system.replset
	rs.reconfig(cfg)                           updates the configuration of a running replica set with cfg (disconnects)
	rs.add(hostportstr)                        add a new member to the set with default attributes (disconnects)
	rs.add(membercfgobj)                       add a new member to the set with extra attributes (disconnects)
	rs.addArb(hostportstr)                     add a new member which is arbiterOnly:true (disconnects)
	rs.stepDown([stepdownSecs, catchUpSecs])   step down as primary (disconnects)
	rs.syncFrom(hostportstr)                   make a secondary sync from the given member
	rs.freeze(secs)                            make a node ineligible to become primary for the time specified
	rs.remove(hostportstr)                     remove a host from the replica set (disconnects)
	rs.slaveOk()                               allow queries on secondary nodes

	rs.printReplicationInfo()                  check oplog size and time range
	rs.printSlaveReplicationInfo()             check replica set members and replication lag
	db.isMaster()                              check who is primary

3.2. Sharding 集群

所谓 Sharding 就是将同一个集合的不同子集分发存储到不同的机器(shard)上,MongoDB 使用 Sharding 机制来支持超大数据量,将不同的 CRUD 路由到不同的机器上执行,以提到数据库的吞吐性能。

这里不重点介绍 Sharding 模式的配置,有兴趣的朋友可参考:https://docs.mongodb.com/manual/sharding/

4. MongoDB Change Streams(可监控数据变化)

Change Streams 是 MongoDB 3.6 中增加的功能。应用程序使用 Change Stream 可以订阅 Collection 的变化。Change Stream 是基于 oplog 实现的,所以这个功能只在 Replica Set 集群或 Sharding 集群中可用。

参考:https://docs.mongodb.com/manual/changeStreams/

4.1. 实例程序

下面是 Change Stream 的实例程序(假设文件名为 test.js):

const MongoClient = require("mongodb").MongoClient;   // npm i mongodb@3

// 假设MongoDB Replica Set集群的名字为rs0,集群服务器为localhost:27017,localhost:27018,localhost:27019
MongoClient.connect("mongodb://localhost:27017,localhost:27018,localhost:27019?replicaSet=rs0")
  .then(client => {
    console.log("Connected correctly to server");
    // specify db and collections
    const db = client.db("testdb1");
    const collection = db.collection("people");

    const changeStream = collection.watch();

    // start listen to changes
    changeStream.on("change", function(change) {
      console.log(change);  // 数据库testdb1中集合people有变化时,这里都会输出
    });
});

上面程序实现了监控数据库 testdb1 中集合 people,一旦有变化就会输出变化内容。

执行上面程序,会输出下面内容后一直等待。

$ node test.js
Connected correctly to server

打开一个 mongo shell,我们往数据库 testdb1 中集合 people 中插入一条数据,如下:

rs0:PRIMARY> use testdb1
switched to db testdb1
rs0:PRIMARY> db.people.insert({"name" : "cig01"})
WriteResult({ "nInserted" : 1 })

这时,运行前面程序的窗口会显示:

Connected correctly to server
{ _id:
   { _data:
      Binary {
        _bsontype: 'Binary',
        sub_type: 0,
        position: 49,
        buffer:
         <Buffer 82 5b e6 98 87 00 00 00 02 46 64 5f 69 64 00 64 5b e6 98 87 3b 60 bc 76 02 ae 0f 37 00 5a 10 04 6a 58 86 6b f4 11 42 b4 8a f6 3a ec 8e 52 86 7b 04> } },
  operationType: 'insert',
  fullDocument: { _id: 5be698873b60bc7602ae0f37, name: 'cig01' },
  ns: { db: 'testdb1', coll: 'people' },
  documentKey: { _id: 5be698873b60bc7602ae0f37 } }

Author: cig01

Created: <2018-08-05 Sun>

Last updated: <2018-11-10 Sat>

Creator: Emacs 27.1 (Org mode 9.4)