阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

MongoDB权威指南第2版学习笔记

175次阅读
没有评论

共计 34422 个字符,预计需要花费 87 分钟才能阅读完成。

MongoDB简介

MongoDB在功能和复杂性之间取得了很好的平衡,并且大大简化了原先十分复杂的任务,它具备支撑今天主流 web 应用的关键功能:索引、复制、分片、丰富的查询语法,特别灵活的数据模型。与此同时还不牺牲速度。

MongoDB是一款强大、灵活,且易于扩展的通用型数据库。能扩展出非常多的功能,如二级索引、范围查询、排序、聚合,以及地理空间索引。

MongoDB 权威指南第 2 版 PDF 完整带书签目录 下载见 http://www.linuxidc.com/Linux/2016-12/138253.htm

设计特点

易于使用

MongoDB是一个面向文档的数据库,比关系型数据库有更好的扩展性。用文档代替行。能够仅使用一条记录来表现发展的层次关系。

文档的键和值不再是固定的类型和大小。

易于扩展

MongoDB的设计采用横向扩展。面向文档的数据模型使它能很容易的在多台服务器之间进行数据分割。

MongoDB能自动处理跨集群的数据和负载,自动重新分配文档,以及将用户请求路由到正确的机器上。如果一个集群需要很大的容量,只需向集群添加新服务器,MongoDB就会自动将现有数据向新服务器传送。

丰富的功能

MongoDB作为一款通用型数据库,除了能够增删改查之外,还有一系列不断扩展的独特功能。

索引

MongoDB支持通用二级索引,允许多种快速查询,且提供唯一索引、符合索引、地理空间索引,以及全文索引。

聚合

MongoDB支持“聚合管道”。用户能通过简单的片段创建复杂的聚合,并通过数据库自动优化。

特殊的集合类型

MongoDB支持存在时间有限的集合,适用于那些将在某个时刻过期的数据,如会话。类似的,MongoDB也支持固定大小的集合,用于保存近期数据,如日志。

文件存储

MongoDB支持一种非常易用的协议,用于存储大文件和文件元数据。

MongoDB不具备连接和复杂的多行事务

卓越的性能

MongoDB能对文档进行动态填充,也能预分配数据文件以利用额外的空间换取稳定的性能。MongoDB把尽可能多的内存用作缓存,试图为每次查询自动选择正确的索引。

MongoDB基础知识

1.文档是 MongoDB 中数据的基本单元,非常类似 mysql 的行,但更具表现力。

2.集合可以看作是一个拥有动态模式的表。

3.MongoDB的一个实例可以拥有多个相互独立的数据库,每一个数据库都拥有自己的集合。

4.每一个文档都有一个特殊的键“_id,在文档所属的集合中是唯一的。

5.MongoDB自带 js shell,可用于管理MongoDB 的实例或数据操作。

文档(类似 mysql 的行)

文档就是键值对的一个有序集。比如:{“greeting”:”hello world!”,”foo”:3},文档的键是字符串。键可以是任意 utf-8 字符。除了少数情况:

  1. 键不能含有 \0( 空字符)。这个字符用于表示键的结尾。
  2. .$ 具有特殊意义,只能在特定环境下使用。

MongoDB不但区分类型,而且区分大小写,{“foo”:3}、{“foo”:”3”}、{“Foo”:3}三个文档是不同的。

MongoDB的文档不能有重复的键。{“foo”:”3”,”foo”,3}文档非法。

文档中的键值对是有序的。{“x”:1,”y”:2}与 {“y”:2,”x”:1} 是不同的。

集合(类似 mysql 中的表)

集合就是一组文档。

动态模式

集合是动态模式的。这意味着一个集合里面的文档可以是各式各样的。可以放置任何文档,既然如此,为何还要把相关类型的文档组织在一起,使用多个集合,而不是通通放在一个集合里呢?原因如下:

  1. 难以管理
  2. 速度不划算,分开查询多个集合要快得多。
  3. 把同种类型的文档放在一个集合里,数据会更加集中。磁盘寻道操作更少。
  4. 在一个集合中只放入一种类型的文档,可以更加有效地对集合进行索引。

命名

集合使用名称进行标识,可以是任意的 utf-8 字符串,除了以下特殊情况:

  1. 不能是空字符串(“”)
  2. 不能包含 \0 字符 ( 空字符),这个字符表示集合名的结束。
  3. 不能以”system.”开头,这是为系统集合保留的前缀。例如:system.users 保存着数据库的用户信息,system.namespaces保存着所有数据库集合的信息。
  4. 不能包含保留字符’$’

子集合

组织集合的一种惯例是使用 .”分隔不同命名空间的子集合。为了使组织结构更清晰,比如(blog.postsblog.authors),虽然子集合没有任何特别的属性,但他们却非常有用。使用子集合来组织数据非常高效。

数据库

多个集合可以组成数据库。一个 MongoDB 实例可以承载多个数据库,每个数据库拥有 0 个或多个集合。每个数据库都有独立的权限。不同的数据库放置在不同的文件中。

命名

数据库最终会变成文件系统里的文件,数据库名就是相应的文件名,因此命名会有很多限制。数据库通过名称来标识。可以是任意的 utf-8 字符串,除了以下特殊情况:

  1.  不能是空字符串(“”)
  2.  不得含有 /\.’’*<>:|?$( 一个空格 )\0( 空字符 )。基本上,只能使用ASCII 中的字母和数字。
  3.  区分大小写,��简单起见,应全部小写。
  4.  数据库名最多 64 字节。

保留数据库名

有一些数据库名是保留的,可以直接访问这些有特殊语义的数据库。

admin

从身份验证的角度看,这是 root”数据库。如果将一个用户添加到了admin 数据库,将自动获得所有数据库的权限。再者,一些特定的服务端命令也只能从 admin 数据库运行,如列出所有数据库或关闭服务器。

 local

 这个数据库永远都不可以复制,且一台服务器上的所有本地集合都可以存储在这个数据库中。

 config

 MongoDB用于分片设置时,分片信息会存储在 config 数据库中。

启动 MongoDB

window 下,如果配置了环境变量,直接使用  mongod  命令即可。

mongod 在没有任何参数的情况下会默认使用数据目录 /data/db (window 系统中为 C:\data\db). 如果不存在或不可写,服务器会启动失败。

启动时,服务器会打印版本和系统信息,然后等待连接。默认监听发 27017 端口。端口被占用,启动将失败。通常是已经有一个实例在运行了。

mongod 还会启动一个非常基本的 http 服务器,监听 28017 端口,因此,通过 http://localhost:28017, 能获取数据库的管理信息。(测试时找不到网页,不知道哪里没对。)

Shell 中 ctrl+c,中止 mongod 的运行。

 MongoDB 权威指南第 2 版学习笔记

  Linux 下,启动方式

[root@yang ~]# cd /usr/local/mongodb/
[root@yang mongodb]# ./bin/mongod --dbpath /home/m17/ --logpath /home/mlog/m17.log --fork --port 27017

其中 –dbpath 指定数据库存放数据目录,–logpath 指定日志存放目录,–fork  指定为后台线程,–port  指定端口。

启动 shell

Shell 中可以使用命令行与 MongoDB 实例交互。可通过它执行管理操作,检查运行实例等等。启动 shell 使用 mongo  命令,启动时,shell 将自动连接 MongoDB 服务器,须确保  mongod  已启动。

 MongoDB 权威指南第 2 版学习笔记

 Linux 下,启动方式:

[root@yang mongodb]# ./bin/mongo
MongoDB shell version: 3.2.10
connecting to: test
Server has startup warnings: 
2016-11-18T20:35:22.477+0800 I CONTROL  [initandlisten] ** WARNING: You are running this process as the root user, which is not recommended.
2016-11-18T20:35:22.477+0800 I CONTROL  [initandlisten] 
2016-11-18T20:35:22.478+0800 I CONTROL  [initandlisten] 
2016-11-18T20:35:22.478+0800 I CONTROL  [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.
2016-11-18T20:35:22.478+0800 I CONTROL  [initandlisten] **        We suggest setting it to 'never'
2016-11-18T20:35:22.478+0800 I CONTROL  [initandlisten] 
2016-11-18T20:35:22.478+0800 I CONTROL  [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/defrag is 'always'.
2016-11-18T20:35:22.478+0800 I CONTROL  [initandlisten] **        We suggest setting it to 'never'
2016-11-18T20:35:22.478+0800 I CONTROL  [initandlisten] 

Shell 是一个功能完备的 js 解释器。

 MongoDB 权威指南第 2 版学习笔记

MongoDB 客户端

Shell 是一个独立的 MongoDB 客户端。启动时,shell 会连到 MongoDB 服务器的 test 数据库,并将数据库连接赋值给全局变量 db。这个变量是通过 shell 访问 MongoDB 的主要入口点。

查看当前指向哪个数据库,使用 db 命令 

MongoDB 权威指南第 2 版学习笔记

Shell 还包含了一些非 js 语法的扩展。这些扩展并不提供额外的功能,而是一些非常棒的语法糖。比如:

 MongoDB 权威指南第 2 版学习笔记

通过 db 变量,可访问其中的集合。几乎所有数据库操作都可以通过 shell 完成。

 MongoDB 权威指南第 2 版学习笔记

Shell 中的基本操作

创建 insert()

> post={... "title":"My Blog Post",
... "content":"Here's my blog post.",
... "date":new Date()
...     }
{"title" : "My Blog Post",
        "content" : "Here's my blog post.","date": ISODate("2016-12-11T13:28:54.930Z")
}
> db.blog.insert(post)
WriteResult({"nInserted" : 1})

读取 find()、findOne() 

find():shell 会自动显示最多 20 个匹配的文档

findOne() 只查看一个文档

> db.blog.find()
{"_id" : ObjectId("584d57e5bf1475cad287df45"), "title" : "My Blog Post", "content" : "Here's my blog post.","date": ISODate("2016-12-11T13:42:52.351Z") }
> db.blog.findOne()
{"_id" : ObjectId("584d57e5bf1475cad287df45"), "title" : "My Blog Post", "content" : "Here's my blog post.","date": ISODate("2016-12-11T13:42:52.351Z") }

更新 update()

接受至少两个参数:

参数 1:限定条件,参数 2:新的文档

> post={... "title":"My Blog Post",
... "content":"Here's my blog post.",
... "date":new Date(),
... "comments":[]
...     }
{"title" : "My Blog Post",
        "content" : "Here's my blog post.","date": ISODate("2016-12-11T13:43:23.118Z"),"comments" : []}
> db.blog.update({title:"My Blog Post"},post)
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{"_id" : ObjectId("584d57e5bf1475cad287df45"), "title" : "My Blog Post", "content" : "Here's my blog post.","date": ISODate("2016-12-11T13:43:23.118Z"),"comments" : []}

删除 remove()

参数:限制条件

无参数,删除所有文档

> db.blog.remove({title:"My Blog Post"})
WriteResult({"nRemoved" : 1 })
> db.blog.find()

更多详情见请继续阅读下一页的精彩内容:http://www.linuxidc.com/Linux/2016-12/138257p2.htm

数据类型

MongoDB在保留 JSON 基本键 / 值对特性的基础上,添加了其他一些数据类型。

null

null用于表示空值或者不存在的字段:{“x”:null} 

布尔型

布尔类型有两个值 truefalse {“x”:true} 

数值

shell默认使用 64 位浮点型数值。{“x”:3.14} 或 {“x”:3} 

对于整型值,可使用 NumberInt 类(表示 4 字节带符号整数)或 NumberLong 类(表示 8 字符带符号整数){“x”:NumberInt(”3”)} {“x”:NumberLong(“3”)} 

字符串

utf-8字符串都可以表示为字符串类型的数据: {“x”:”foobar”} 

日期

日期被存储为自新世纪以来经过的毫秒数,不存储时区:{“x”:new Date()} 

创建日期对象时,应使用 new Date() ,而非 Date(), 后者返回的是日期的字符串表示,而非日期 Date对象。这个结果与 MongoDB 无关,是 js 的工作机制决定的。由于日期和字符串之间无法匹配,所以执行删改查等几乎所有操作时会导致很多问题。

> db.blog.insert({"x":Date()})
WriteResult({"nInserted" : 1 })
> db.blog.find()
{"_id" : ObjectId("584ea42d3bb80d550a5914fe"), "x" : "Mon Dec 12 2016 21:20:45 GMT+0800 (CST)" }
> db.blog.insert({"x":new Date()})
WriteResult({"nInserted" : 1 })
> db.blog.find()
{"_id" : ObjectId("584ea42d3bb80d550a5914fe"), "x" : "Mon Dec 12 2016 21:20:45 GMT+0800 (CST)" }
{"_id" : ObjectId("584ea4573bb80d550a5914ff"), "x" : ISODate("2016-12-12T13:21:27.361Z") }

正则表达式

查询时,使用正则表达式作为限定条件 {“x”:/foobar/i} 

数组

数据列表或数组集可以表示为数组:{“x”:[“a”,”b”,”c”]} 

数组是一组值,它既能作为有序对象(如列表、栈或队列),也能作为无序对象(如数据集)来操作。

数组中可包含不同数据类型的元素 {“things”:[“pie”,3.14]}。只要是常规的键 / 值对支持的所有值都可以作为数组的值,数组中甚至可以嵌套数组。

内嵌文档

文档可内嵌其他文档,被嵌套的文档作为父文档的值:{“x”:{“foo”,”bar”}} 

使用内嵌文档,可以使数据组织更加自然,不用非得存成扁平结构的键 / 值对。

> post={... "user":{... "name":"yyb",
... "address":{... "street":"123 Park Street",
... "city":"Anytown",
... "state":"NY"
...     }
...   }
... }
{"user" : {"name" : "yyb",
                "address" : {"street" : "123 Park Street",
                        "city" : "Anytown",
                        "state" : "NY"
                }
        }
}
> db.blog.insert(post)
WriteResult({"nInserted" : 1 })
> db.blog.find()
{"_id" : ObjectId("584ea90fce4f8e1bf30ba07d"), "user" : {"name" : "yyb", "address" : { "street" : "123 Park Street", "city" : "Anytown", "state" : "NY"} } }

对象id

对象 id 是一个 12 字节的ID,是文档的唯一标识 {“x”:ObjectId()} 

MongoDB中存储的文档必须有一个“_id”键。确保集合中的每个文档都能被唯一标识。

这个键的值可以是任意类型的,默认是个 ObjectId 对象。对象 id 设计成轻量型的,不同的机器都能用全局唯一的同种方法方便的生成它。这对于在分片环境中生成唯一的标示符非常重要。

ObjectId的前 4 个字节是从标准纪元开始的时间戳,单位为秒。这会带来一些有用的属性。

  • 时间戳,与随后的 5 字节组合起来,提供了秒级别的唯一性。
  • 由于时间戳在前,这意味着 objectid 大致会按插入的顺序排列。
  • 4 字节也隐含了创建文档的时间。绝大多数驱动程序都会提供一个方法,用于从 objectId 获取这些信息。

接下来的 3 字节是所在主机的唯一标识符。通常是机器主机名的散列值。这样就可以确保不同主机生成不同的objectId,不产生冲突。

为了确保在同一机器上并发的多个进程产生的 ObjectId 是唯一的,接下来的两个字节来自产生 objectId 的进程的进程标识符PID

9 字节保证了同一秒钟不同机器不同进程产生的 ObjectId 是唯一的。最后 3 字节是一个自动增加的计数器,确保相同进程同一秒产生的 ObjectId 也是不一样的。一秒钟最多允许每个进程拥有 2563 个不同的ObjectId

如果插入文档时没有 _id”键,系统会自动帮你创建一个。可以由MongoDB 服务完成,但通常会在客户端由驱动程序完成。

二进制数据

二进制数据是一个任意字节的字符串。它不能直接在 shell 中使用。如果要将非 utf-8 字符保存在数据库中,二进制数据是唯一的方式。

代码

查询和文档中可以包含任意 js 代码 {“x”:function(){/**/}} 

另外,有几种大多数情况仅在内部使用(或被其他类型取代)的类型。

使用MongoDB shell

shell 连接到任何 MongoDB 实例:mongo 机器名: 端口 / 数据库名 

下面使用我的 Linux 连接我的 Windows

[root@yang mongodb]# ./bin/mongo 192.168.1.111:27017/test 
MongoDB shell version: 3.2.10
connecting to: 192.168.1.111:27017/test
Server has startup warnings: 
2016-12-12T14:14:40.558+0800 I CONTROL  [initandlisten] 
2016-12-12T14:14:40.558+0800 I CONTROL  [initandlisten] ** WARNING: Access control is not enabled for the database.
2016-12-12T14:14:40.558+0800 I CONTROL  [initandlisten] **          Read and write access to data and configuration is unrestricted.
2016-12-12T14:14:40.558+0800 I CONTROL  [initandlisten] 
> show dbs
admin  0.000GB
local  0.000GB
test   0.000GB
> db.test.find()
> db.test.insert({"x":100})
WriteResult({"nInserted" : 1 })
> 
> db.test.find()
{"_id" : ObjectId("584fe47a36b03fda10897c99"), "x" : 100 }

在我的 windows 中查看效果

MongoDB 权威指南第 2 版学习笔记

启动时不连接任何数据库,使用 –nodb 参数启动 shell, 启动之后,在需要时运行 new Mongo(hostname) 命令就可以连接到想要的 mongod 了。任何时候都可以使用这些命令来连接到不同的数据库或者服务器。

[root@yang mongodb]# ./bin/mongo --nodb
MongoDB shell version: 3.2.10
> conn=new Mongo("192.168.1.111:27017")
connection to 192.168.1.111:27017
> db=conn.getDB("test")
test
> db.test.find()
{"_id" : ObjectId("584fe47a36b03fda10897c99"), "x" : 100 }

Shell内置了帮助文档,可以使用 help 命令查看,可以通过 db.help() 查看数据库级别的帮助,使用 db.foo.help() 查看集合级别的帮助 ,使用db.foo.update 方式查看函数的 js 实现代码。

> 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

使用 shell 执行脚本

mongo shell会依次执行传入的脚本,然后退出。

E:\Program Files\MongoDB\Server\3.4\bin>mongo js1.js
MongoDB shell version v3.4.0-rc3-7-ge24e12c
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.0-rc3-7-ge24e12c
I am yyb in the script1.js

首先:脚本的位置须放在 mongo.exe 所在的目录,也就是 bin 目录下。其次如果配置了环境变量,直接 mongo js1.js 是不行的。比如:

C:\Users\yang>mongo js1.js
2016-12-13T21:25:36.265+0800 E - [main] file [js1.js] doesn't exist
failed to load: js1.js

使用指定的主机 / 端口上的 mongod 运行脚本,需要先指定地址,然后再跟上脚本的文件的名称。

在脚本中使用 print()函数将内容输出到标准输出,这样就可以在 shell 中使用管道命令。如果将 shell 脚本的输出管道给另一个使用 –quiet 选项的命令,就可以让 shell 不打印“MongoDB shell version…”提示。

[root@yang bin]# ./mongo --quiet 192.168.1.111:27017/test js1.js
I like to read and study

可以使用 load() 函数,从交互式 shell 中运行脚本:

> load("js1.js")
I like to read and study
true

在脚本中可以访问 db 变量,以及其他全局变量。然而,shell辅助函数(比如:“use db”和“show collections”)不可以在文件中使用。不过,这些辅助函数都有对应的 js 函数。

MongoDB 权威指南第 2 版学习笔记

可以使用脚本将变量注入到shell

/**
* 连接到指定的数据库,并且将 db 指向这个连接
*/
var connectTo=function(port,dbname){if(!port){port=27017;
}
if (!dbname) {dbname="test";
}
db=connect("localhost:"+port+"/"+dbname);
return db;
}

如果在 shell 中加载这个脚本,connectTo 函数就可以使用了。

> typeof connectTo
undefined
> load("defineConnectTo.js")
true
> typeof connectTo
function

除了添加辅助函数,还可以使用脚本将通用的任务和管理活动自动化。默认情况,shell会在运行 shell 时所处的目录中查找脚本(可以使用 run(“pwd”) 命令查看)。可以为 shell 指定一个相对路径或者绝对路径。

> load("/usr/local/mongodb/js1.js")
i am studing mongodb
2016-12-14T19:36:01.744+0800 I -        [thread1] shell: started program (sh6054):  pwd
sh6054| /usr/local/mongodb
true

也可以在 shell 中使用 run() 函数来执行命令行程序。可以在函数参数列表中指定程序所需的参数。通常来说,这种使用方式的局限性非常大,因为输出格式很奇怪,而且不支持管道。

> run("ls","-l","/usr/local/mongodb/")
2016-12-14T19:40:23.878+0800 I -        [thread1] shell: started program (sh6098):  ls -l /usr/local/mongodb/
sh6098| 总用量 104
sh6098| drwxrwxr-x. 2 yang yang  4096 12 月 13 23:31 bin
sh6098| -rw-r--r--. 1 yang yang 34520 9 月  30 18:46 GNU-AGPL-3.0
sh6098| -rw-r--r--. 1 root root    43 12 月 14 19:34 js1.js
sh6098| -rw-r--r--. 1 yang yang 16726 9 月  30 18:46 MPL-2
sh6098| -rw-r--r--. 1 yang yang  1359 9 月  30 18:46 README
sh6098| -rw-r--r--. 1 yang yang 35910 9 月  30 18:46 THIRD-PARTY-NOTICES
0

创建 .mongorc.js 文件

如果某些脚本会被频繁加载,可以将它们添加到 mongorc.js 文件中。这个文件会在启动 shell 时 自动运行。

Win10中,这个文件所在位置:C:\Users\yang ,Linux中,不知道哪个是用户主目录,对 linux 不熟悉。在 home 目录中创建了这个文件没什么效果。

C:\Windows\system32>mongo
MongoDB shell version v3.4.0-rc3-7-ge24e12c
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.0-rc3-7-ge24e12c
Hello,i am studing mongodb// 在文件中写了 print 这句话
Server has startup warnings:
2016-12-12T14:14:40.558+0800 I CONTROL  [initandlisten]
2016-12-12T14:14:40.558+0800 I CONTROL  [initandlisten] ** WARNING: Access control is not enabled for the database.
2016-12-12T14:14:40.558+0800 I CONTROL  [initandlisten] **          Read and write access to data and configuration is unrestricted.
2016-12-12T14:14:40.558+0800 I CONTROL  [initandlisten]
>

可以使用这个脚本创建一些自己需要的全局变量,或者是为太长的名字创建一个简短的别名,也可以重写内置的函数。最常见的用途之一是移除那些比较 “危险”的shell 辅助函数。

改变数据库函数时,要确保同时对 db 变量和 DB 原型进行改变。如果只改变了其中一个,那么 db 变量可能没有改变,或者这些改变在新使用的所有数据库中都不会生效。比如:

var no=function(){print("Not on my watch.");    
}
//禁止删除数据库
db.dropDatebase=DB.prototype.dropDatabase=no;
//禁止删除集合
DBCollection.prototype.drop=no;
//禁止删除索引
DBCollection.prototype.dropIndex=no;

如果在启动 shell 时指定 –norc 参数,就可以禁止加载.mongorc.js

可以在 .mongorc.js 中定制自己想要的提示。将 prompt 变量设为一个字符串或者返回字符串的函数。就可以重写默认的 shell 提示。也可以定制多个提示,在 shell 中可以自由切换。

MongoDB 权威指南第 2 版学习笔记

C:\Windows\system32>mongo
MongoDB shell version v3.4.0-rc3-7-ge24e12c
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.0-rc3-7-ge24e12c
Server has startup warnings:
2016-12-12T14:14:40.558+0800 I CONTROL  [initandlisten]
2016-12-12T14:14:40.558+0800 I CONTROL  [initandlisten] ** WARNING: Access control is not enabled for the database.
2016-12-12T14:14:40.558+0800 I CONTROL  [initandlisten] **          Read and write access to data and configuration is unrestricted.
2016-12-12T14:14:40.558+0800 I CONTROL  [initandlisten]
test>
shell 中设置 EDITOR 变量调用编辑器,在 windows 中貌似不认。
> EDITOR="/usr/bin/emacs"
> var wap=db.books.findOne({title:"war and peace"})
> edit wap

.mongorc.js 文件中添加一行 EDITOR=” 编辑器路径 “;, 以后就不必单独设了。

集合命名注意事项

可以使用 db.collectionName 获取一个集合的内容,但是,如果集合名称重包含保留字或者无效的 js 属性名称,就不行了。

> db.test
test.test
> db.version
function () {
       
return this.serverBuildInfo().version;
    }

如果集合中包含无效的 js 属性名称 , 可以使用 getCollection 函数:

> db.getCollection("version")
test.version

也可以使用数组访问语法,访问以无效属性命名的集合。

> collections=[“po-sts”,”123comments”,”authors”];
[“po-sts”, “123comments”, “authors”]
> for(var i in collections){print(db.blog[collections[i]]); }
blog.blog.po-sts
blog.blog.123comments
blog.blog.authors

MongoDB简介

MongoDB在功能和复杂性之间取得了很好的平衡,并且大大简化了原先十分复杂的任务,它具备支撑今天主流 web 应用的关键功能:索引、复制、分片、丰富的查询语法,特别灵活的数据模型。与此同时还不牺牲速度。

MongoDB是一款强大、灵活,且易于扩展的通用型数据库。能扩展出非常多的功能,如二级索引、范围查询、排序、聚合,以及地理空间索引。

MongoDB 权威指南第 2 版 PDF 完整带书签目录 下载见 http://www.linuxidc.com/Linux/2016-12/138253.htm

设计特点

易于使用

MongoDB是一个面向文档的数据库,比关系型数据库有更好的扩展性。用文档代替行。能够仅使用一条记录来表现发展的层次关系。

文档的键和值不再是固定的类型和大小。

易于扩展

MongoDB的设计采用横向扩展。面向文档的数据模型使它能很容易的在多台服务器之间进行数据分割。

MongoDB能自动处理跨集群的数据和负载,自动重新分配文档,以及将用户请求路由到正确的机器上。如果一个集群需要很大的容量,只需向集群添加新服务器,MongoDB就会自动将现有数据向新服务器传送。

丰富的功能

MongoDB作为一款通用型数据库,除了能够增删改查之外,还有一系列不断扩展的独特功能。

索引

MongoDB支持通用二级索引,允许多种快速查询,且提供唯一索引、符合索引、地理空间索引,以及全文索引。

聚合

MongoDB支持“聚合管道”。用户能通过简单的片段创建复杂的聚合,并通过数据库自动优化。

特殊的集合类型

MongoDB支持存在时间有限的集合,适用于那些将在某个时刻过期的数据,如会话。类似的,MongoDB也支持固定大小的集合,用于保存近期数据,如日志。

文件存储

MongoDB支持一种非常易用的协议,用于存储大文件和文件元数据。

MongoDB不具备连接和复杂的多行事务

卓越的性能

MongoDB能对文档进行动态填充,也能预分配数据文件以利用额外的空间换取稳定的性能。MongoDB把尽可能多的内存用作缓存,试图为每次查询自动选择正确的索引。

MongoDB基础知识

1.文档是 MongoDB 中数据的基本单元,非常类似 mysql 的行,但更具表现力。

2.集合可以看作是一个拥有动态模式的表。

3.MongoDB的一个实例可以拥有多个相互独立的数据库,每一个数据库都拥有自己的集合。

4.每一个文档都有一个特殊的键“_id,在文档所属的集合中是唯一的。

5.MongoDB自带 js shell,可用于管理MongoDB 的实例或数据操作。

文档(类似 mysql 的行)

文档就是键值对的一个有序集。比如:{“greeting”:”hello world!”,”foo”:3},文档的键是字符串。键可以是任意 utf-8 字符。除了少数情况:

  1. 键不能含有 \0( 空字符)。这个字符用于表示键的结尾。
  2. .$ 具有特殊意义,只能在特定环境下使用。

MongoDB不但区分类型,而且区分大小写,{“foo”:3}、{“foo”:”3”}、{“Foo”:3}三个文档是不同的。

MongoDB的文档不能有重复的键。{“foo”:”3”,”foo”,3}文档非法。

文档中的键值对是有序的。{“x”:1,”y”:2}与 {“y”:2,”x”:1} 是不同的。

集合(类似 mysql 中的表)

集合就是一组文档。

动态模式

集合是动态模式的。这意味着一个集合里面的文档可以是各式各样的。可以放置任何文档,既然如此,为何还要把相关类型的文档组织在一起,使用多个集合,而不是通通放在一个集合里呢?原因如下:

  1. 难以管理
  2. 速度不划算,分开查询多个集合要快得多。
  3. 把同种类型的文档放在一个集合里,数据会更加集中。磁盘寻道操作更少。
  4. 在一个集合中只放入一种类型的文档,可以更加有效地对集合进行索引。

命名

集合使用名称进行标识,可以是任意的 utf-8 字符串,除了以下特殊情况:

  1. 不能是空字符串(“”)
  2. 不能包含 \0 字符 ( 空字符),这个字符表示集合名的结束。
  3. 不能以”system.”开头,这是为系统集合保留的前缀。例如:system.users 保存着数据库的用户信息,system.namespaces保存着所有数据库集合的信息。
  4. 不能包含保留字符’$’

子集合

组织集合的一种惯例是使用 .”分隔不同命名空间的子集合。为了使组织结构更清晰,比如(blog.postsblog.authors),虽然子集合没有任何特别的属性,但他们却非常有用。使用子集合来组织数据非常高效。

数据库

多个集合可以组成数据库。一个 MongoDB 实例可以承载多个数据库,每个数据库拥有 0 个或多个集合。每个数据库都有独立的权限。不同的数据库放置在不同的文件中。

命名

数据库最终会变成文件系统里的文件,数据库名就是相应的文件名,因此命名会有很多限制。数据库通过名称来标识。可以是任意的 utf-8 字符串,除了以下特殊情况:

  1.  不能是空字符串(“”)
  2.  不得含有 /\.’’*<>:|?$( 一个空格 )\0( 空字符 )。基本上,只能使用ASCII 中的字母和数字。
  3.  区分大小写,��简单起见,应全部小写。
  4.  数据库名最多 64 字节。

保留数据库名

有一些数据库名是保留的,可以直接访问这些有特殊语义的数据库。

admin

从身份验证的角度看,这是 root”数据库。如果将一个用户添加到了admin 数据库,将自动获得所有数据库的权限。再者,一些特定的服务端命令也只能从 admin 数据库运行,如列出所有数据库或关闭服务器。

 local

 这个数据库永远都不可以复制,且一台服务器上的所有本地集合都可以存储在这个数据库中。

 config

 MongoDB用于分片设置时,分片信息会存储在 config 数据库中。

启动 MongoDB

window 下,如果配置了环境变量,直接使用  mongod  命令即可。

mongod 在没有任何参数的情况下会默认使用数据目录 /data/db (window 系统中为 C:\data\db). 如果不存在或不可写,服务器会启动失败。

启动时,服务器会打印版本和系统信息,然后等待连接。默认监听发 27017 端口。端口被占用,启动将失败。通常是已经有一个实例在运行了。

mongod 还会启动一个非常基本的 http 服务器,监听 28017 端口,因此,通过 http://localhost:28017, 能获取数据库的管理信息。(测试时找不到网页,不知道哪里没对。)

Shell 中 ctrl+c,中止 mongod 的运行。

 MongoDB 权威指南第 2 版学习笔记

  Linux 下,启动方式

[root@yang ~]# cd /usr/local/mongodb/
[root@yang mongodb]# ./bin/mongod --dbpath /home/m17/ --logpath /home/mlog/m17.log --fork --port 27017

其中 –dbpath 指定数据库存放数据目录,–logpath 指定日志存放目录,–fork  指定为后台线程,–port  指定端口。

启动 shell

Shell 中可以使用命令行与 MongoDB 实例交互。可通过它执行管理操作,检查运行实例等等。启动 shell 使用 mongo  命令,启动时,shell 将自动连接 MongoDB 服务器,须确保  mongod  已启动。

 MongoDB 权威指南第 2 版学习笔记

 Linux 下,启动方式:

[root@yang mongodb]# ./bin/mongo
MongoDB shell version: 3.2.10
connecting to: test
Server has startup warnings: 
2016-11-18T20:35:22.477+0800 I CONTROL  [initandlisten] ** WARNING: You are running this process as the root user, which is not recommended.
2016-11-18T20:35:22.477+0800 I CONTROL  [initandlisten] 
2016-11-18T20:35:22.478+0800 I CONTROL  [initandlisten] 
2016-11-18T20:35:22.478+0800 I CONTROL  [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.
2016-11-18T20:35:22.478+0800 I CONTROL  [initandlisten] **        We suggest setting it to 'never'
2016-11-18T20:35:22.478+0800 I CONTROL  [initandlisten] 
2016-11-18T20:35:22.478+0800 I CONTROL  [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/defrag is 'always'.
2016-11-18T20:35:22.478+0800 I CONTROL  [initandlisten] **        We suggest setting it to 'never'
2016-11-18T20:35:22.478+0800 I CONTROL  [initandlisten] 

Shell 是一个功能完备的 js 解释器。

 MongoDB 权威指南第 2 版学习笔记

MongoDB 客户端

Shell 是一个独立的 MongoDB 客户端。启动时,shell 会连到 MongoDB 服务器的 test 数据库,并将数据库连接赋值给全局变量 db。这个变量是通过 shell 访问 MongoDB 的主要入口点。

查看当前指向哪个数据库,使用 db 命令 

MongoDB 权威指南第 2 版学习笔记

Shell 还包含了一些非 js 语法的扩展。这些扩展并不提供额外的功能,而是一些非常棒的语法糖。比如:

 MongoDB 权威指南第 2 版学习笔记

通过 db 变量,可访问其中的集合。几乎所有数据库操作都可以通过 shell 完成。

 MongoDB 权威指南第 2 版学习笔记

Shell 中的基本操作

创建 insert()

> post={... "title":"My Blog Post",
... "content":"Here's my blog post.",
... "date":new Date()
...     }
{"title" : "My Blog Post",
        "content" : "Here's my blog post.","date": ISODate("2016-12-11T13:28:54.930Z")
}
> db.blog.insert(post)
WriteResult({"nInserted" : 1})

读取 find()、findOne() 

find():shell 会自动显示最多 20 个匹配的文档

findOne() 只查看一个文档

> db.blog.find()
{"_id" : ObjectId("584d57e5bf1475cad287df45"), "title" : "My Blog Post", "content" : "Here's my blog post.","date": ISODate("2016-12-11T13:42:52.351Z") }
> db.blog.findOne()
{"_id" : ObjectId("584d57e5bf1475cad287df45"), "title" : "My Blog Post", "content" : "Here's my blog post.","date": ISODate("2016-12-11T13:42:52.351Z") }

更新 update()

接受至少两个参数:

参数 1:限定条件,参数 2:新的文档

> post={... "title":"My Blog Post",
... "content":"Here's my blog post.",
... "date":new Date(),
... "comments":[]
...     }
{"title" : "My Blog Post",
        "content" : "Here's my blog post.","date": ISODate("2016-12-11T13:43:23.118Z"),"comments" : []}
> db.blog.update({title:"My Blog Post"},post)
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{"_id" : ObjectId("584d57e5bf1475cad287df45"), "title" : "My Blog Post", "content" : "Here's my blog post.","date": ISODate("2016-12-11T13:43:23.118Z"),"comments" : []}

删除 remove()

参数:限制条件

无参数,删除所有文档

> db.blog.remove({title:"My Blog Post"})
WriteResult({"nRemoved" : 1 })
> db.blog.find()

更多详情见请继续阅读下一页的精彩内容:http://www.linuxidc.com/Linux/2016-12/138257p2.htm

增删改文档

插入 insert

单条插入

> db.foo.insert({"bar":"baz"})
WriteResult({"nInserted" : 1 })

批量插入

> db.foo.insert([{"_id":1},{"_id":2},{"_id":3}])
BulkWriteResult({"writeErrors" : [ ],
        "writeConcernErrors" : [ ],
        "nInserted" : 3,
        "nUpserted" : 0,
        "nMatched" : 0,
        "nModified" : 0,
        "nRemoved" : 0,
        "upserted" : []})
> db.foo.find()
{"_id" : 1 }
{"_id" : 2 }
{"_id" : 3 }
>

如果在执行批量插入的过程中有一个文档插入失败,那么在这个文档之前的所有文档都会插入成功,之后的所有全部失败。

> db.foo.insert([{"_id":10},{"_id":11},{"_id":10},{"_id":12}])
BulkWriteResult({"writeErrors" : [
                {"index" : 2,
                        "code" : 11000,
                        "errmsg" : "E11000 duplicate key error collection: test.foo index: _id_ dup key: {: 10.0}",
                        "op" : {"_id" : 10
                        }
                }
        ],
        "writeConcernErrors" : [ ],
        "nInserted" : 2,
        "nUpserted" : 0,
        "nMatched" : 0,
        "nModified" : 0,
        "nRemoved" : 0,
        "upserted" : []})
> db.foo.find()
{"_id" : 10 }
{"_id" : 11 }
>

删除文档

remove

remove函数接受一个查询文档作为参数。符合条件的文档才被删除。删除数据是永久性的,不能撤销,也不能恢复。

> db.foo.remove()
2016-12-15T19:50:31.721+0800 E QUERY    [thread1] Error: remove needs a query :
DBCollection.prototype._parseRemove@src/mongo/shell/collection.js:406:1
DBCollection.prototype.remove@src/mongo/shell/collection.js:433:18
@(shell):1:1

> db.foo.remove({"_id":10})
WriteResult({"nRemoved" : 1 })
> db.foo.find()
{"_id" : 11 }
>

drop

要清空整个集合,那么使用 drop 直接删除集合会更快。代价是:不能指定任何限定条件。整个集合都被删除,所有元数据都不见了。

> for(var i=0;i<1000000;i++){... db.tester.insert({"foo":"bar","baz":i,"z":10-i})
... }
WriteResult({"nInserted" : 1 })
> db.tester.find()
{"_id" : ObjectId("58528543b049609a5fa74f7c"), "foo" : "bar", "baz" : 0, "z" : 10 }
......
Type "it" for more > db.tester.drop()// 插入一百万条数据,使用 drop 删除, 只需 1ms true >

更新文档update

Update有两个必须参数:

一是查询文档,用于定位需要更新的目标文档

二是修改器文档,用于说明要找到的文档进行哪些修改

更新操作是不可分割的:若是两个更新同时发生,先到达服务器的先执行,接着执行另一个。

db.foo.insert({
name:yyb,
friends:32,
enemies:2
… })
WriteResult({
nInserted : 1 })
> db.foo.update({name:yyb},{name:joe})// 将 yyb 这个文档修改成{“name”:“joe”}
WriteResult({
nMatched : 1, nUpserted : 0, nModified : 1 })
> db.foo.find()
{
_id : ObjectId(58528a2bb049609a5fb691bc), name : joe }
>

文档替换

用一个新文档完全替换匹配的文档,这适合大规模模式迁移的情况。

  db.user.insert({... ... "name":"joe",
... ... "friends":32,
... ... "enemies":2
... ... })
WriteResult({"nInserted" : 1 })
//将上面这个文档的后两个字段移到子文档为 realtionships 中
> var joe=db.user.findOne({"name":"joe"})
> joe.relationships={"friends":joe.friends,"enemies":joe.enemies};
{"friends" : 32, "enemies" : 2 }
> delete joe.friends
true
> delete joe.enemies
true
> db.user.update({"name":"joe"},joe);
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.user.find()
{"_id" : ObjectId("58529188b049609a5fb691bf"), "name" : "joe", "relationships" : {"friends" : 32, "enemies" : 2 } }
>

常见的错误是查询条件匹配到了多个文档,然后更新时由于第二个参数的存在就产生重复的 _id 值。数据库会抛出异常。

> db.user.find()
{"_id" : ObjectId("585295e3b049609a5fb691c5"), "name" : "yyb", "age" : 21 }
{"_id" : ObjectId("585295e3b049609a5fb691c6"), "name" : "yyb", "age" : 30 }
{"_id" : ObjectId("585295e3b049609a5fb691c7"), "name" : "yyb", "age" : 40 }
> joe=db.user.findOne({"name":"yyb","age":30})
{"_id" : ObjectId("585295e3b049609a5fb691c6"), "name" : "yyb", "age" : 30 }
> joe.age++;
30
> db.user.update({"name":"yyb"},joe)
WriteResult({"nMatched" : 0,
        "nUpserted" : 0,
        "nModified" : 0,
        "writeError" : {"code" : 16837,
                "errmsg" : "The _id field cannot be changed from {_id: ObjectId('585295e3b049609a5fb691c5')} to {_id: ObjectId('585295e3b049609a5fb691c6')}."
        }
})
>

使用修改器

使用原子性的更新修改器,指定对文档的某些字段进行更新。更新修改器是种特殊的键,用来指定复杂的更新操作,比如修改、添加或者删除键,还可能是操作数组或者内嵌文档。

> db.user.find()
{"_id" : ObjectId("585295e3b049609a5fb691c5"), "name" : "yyb", "age" : 21 }
{"_id" : ObjectId("585295e3b049609a5fb691c6"), "name" : "yyb", "age" : 30 }
{"_id" : ObjectId("585295e3b049609a5fb691c7"), "name" : "yyb", "age" : 40 }
> db.user.update({"name":"yyb"},{$inc:{"age":5}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })> db.user.find({"name":"yyb"})
{"_id" : ObjectId("585295e3b049609a5fb691c5"), "name" : "yyb", "age" : 26 }
{"_id" : ObjectId("585295e3b049609a5fb691c6"), "name" : "yyb", "age" : 30 }
{"_id" : ObjectId("585295e3b049609a5fb691c7"), "name" : "yyb", "age" : 40 }
>

明明匹配 3 条,却只改了一条。原来 MongoDB 默认只会更新匹配的第一条,如果要更新多条,还得指定参数。

使用修改器时,_id的值不能改变。(整个文档替换时可以改变“_id”)

$set 与 $unset

用来指定一个字段的值。如果这个字段不存在,则创建它。

> db.user.insert({... "name":"yyb",
... "age":20,
... "sex":"male",
... "location":"cd"})
WriteResult({"nInserted" : 1 })
>> db.user.update({"name":"yyb"},{"$set":{"email":"123@qq.com"}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.user.find().pretty()
{"_id" : ObjectId("58529e66b049609a5fb691c9"),
        "name" : "yyb",
        "age" : 20,
        "sex" : "male",
        "location" : "cd",
        "email" : "123@qq.com"
}
>

用 $set 甚至可以修改键的类型。用 $unset 可以将键完全删除。

>  db.user.update(... ... {"name":"yyb"},
... ... {"$set":{"email":["xx@qq.com","xl@sina.com"]}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.user.find().pretty()
{"_id" : ObjectId("58529e66b049609a5fb691c9"),
        "name" : "yyb",
        "age" : 20,
        "sex" : "male",
        "location" : "cd",
        "email" : ["xx@qq.com",
                "xl@sina.com"
        ]
}
> db.user.update({"name":"yyb"},{"$unset":{"email":1}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.user.find().pretty()
{"_id" : ObjectId("58529e66b049609a5fb691c9"),
        "name" : "yyb",
        "age" : 20,
        "sex" : "male",
        "location" : "cd"
}

也可以用 $set 修改内嵌文档:

{"_id" : ObjectId("5853e17ff7720722b4ded850"),
        "title" : "a blog post",
        "content" : "...",
        "author" : {"name" : "yyb",
                "email" : "aaa@sina.com"
        }
}
> db.blog.update(... {"author.name":"yyb"},
... {"$set":{"author.name":"joe"}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.findOne()
{"_id" : ObjectId("5853e17ff7720722b4ded850"),
        "title" : "a blog post",
        "content" : "...",
        "author" : {"name" : "joe",
                "email" : "aaa@sina.com"
        }
}

增加、删除、修改键时,应该使用 $ 开头的修改器,否则可能会将整个文档替换掉。

$inc

 $inc 用来增加已有键的值,或者该键不存在那就创建一个。对于更新分析数据、因果关系、投票或者其他有变化数值的地方很方便。

> db.games.insert({"games":"pinball","user":"joe"})
WriteResult({"nInserted" : 1 })
> db.games.update({"games":"pinball"},{"$inc":{"score":50}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.games.findOne()
{"_id" : ObjectId("5853e517f7720722b4ded851"),
        "games" : "pinball",
        "user" : "joe",
        "score" : 50
}
> db.games.update({"games":"pinball"},{"$inc":{"score":10000}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.games.findOne()
{"_id" : ObjectId("5853e517f7720722b4ded851"),
"games" : "pinball",
"user" : "joe",
"score" : 10050
}
>

 $inc 就是专门用来增减数字的。且只能用于整型、长整型或者双精度浮点型的值。其他类型的数据会操作失败。

> db.foo.insert({"count":"1"})
WriteResult({"nInserted" : 1 })

> db.foo.update({},{"$inc":{"count":1}})
WriteResult({"nMatched" : 0,
        "nUpserted" : 0,
        "nModified" : 0,
        "writeError" : {"code" : 16837,
                "errmsg" : "Cannot apply $inc to a value of non-numeric type. {_id: ObjectId('5853e73df7720722b4ded853')} has the field'count'of non-numeric type String"
        }
})
>

 $inc 键的值必须为数字 ,不能使用字符串、数组或者其他非数字的值。要修改其他类型,应该使用 $set  或者数字修改器。

> db.foo.insert({"count":1})
WriteResult({"nInserted" : 1 })

> db.foo.update({},{"$inc":{"count":"5"}})
WriteResult({"nMatched" : 0,
        "nUpserted" : 0,
        "nModified" : 0,
        "writeError" : {"code" : 14,
                "errmsg" : "Cannot increment with non-numeric argument: {count: \"5\"}"
        }
})
>

数组修改器

$push

$push 添加元素。如果数组已经存在,会向已有的数组末尾加入一个元素,要是没有就创建一个新的数组。

> db.blog.post.findOne()
{"_id" : ObjectId("5853ea01f7720722b4ded855"),
        "title" : "a blog post",
        "content" : "..."
}
> db.blog.post.update(... {"title":"a blog post"},
... {"$push":{"comments":{"name":"joe","email":"joe@qq.com","content":"nice post"}}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.post.findOne()
{"_id" : ObjectId("5853ea01f7720722b4ded855"),
        "title" : "a blog post",
        "content" : "...",
        "comments" : [
                {"name" : "joe",
                        "email" : "joe@qq.com",
                        "content" : "nice post"
                }
        ]
}
>
 
 
> db.blog.post.update(... {"title":"a blog post"},
... {"$push":{"comments":{"name":"bob","email":"bob@sina.com","content":"good post."}}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.post.findOne()
{"_id" : ObjectId("5853ea01f7720722b4ded855"),
        "title" : "a blog post",
        "content" : "...",
        "comments" : [
                {"name" : "joe",
                        "email" : "joe@qq.com",
                        "content" : "nice post"
                },
                {"name" : "bob",
                        "email" : "bob@sina.com",
                        "content" : "good post."
                }
        ]
}
>

$each

可以将它应用在一些比较复杂的数组操作中。使用 $each 子操作符,可以通过一次 $push 操作添加多个值。

比如:下面将三个新元素添加到数组中。如果指定的数组中只包含一个元素,那么等同于和没有使用$each”的普通的“$push”操作。

db.stock.ticket.insert({"_id":"goog"})
WriteResult({"nInserted" : 1 })
>  db.stock.ticket.update(... ... ... {"_id":"goog"},
... ... ... {"$push":{"hourly":{"$each":[562.776,562.790,559.123]}}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.stock.ticket.findOne()
{"_id" : "goog", "hourly" : [562.776, 562.79, 559.123 ] }

要实现上面的操作,下面方法也可以。

db.stock.ticket.update(... ... ... ... ... {"_id":"goog"},
... ... ... ... ... {"$set":{"hourly":[562.776,562.790,559.123]}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.stock.ticket.findOne()
{"_id" : "goog", "hourly" : [562.776, 562.79, 559.123 ] }

而下面这样是不行的

 db.stock.ticket.update(... ... ... ... {"_id":"goog"},
... ... ... ... {"$push":{"hourly":[562.776,562.790,559.123]}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.stock.ticket.findOne()
{"_id" : "goog", "hourly" : [[ 562.776, 562.79, 559.123 ] ] }

$slice

如果希望数组的最大长度是固定的,那么可以将 $slice 和 $push 组合在一起使用,就可以保证数组不会超出设定好的最大长度。$slice 的值必须是负整数。

假设 $slice 的值为 10,如果$push  后的数组的元素个数小于 10,那么所有元素都会保留。反之,只有最后那10 个元素会保留。因此,$slice 可以用来在文档中创建一个队列。

db.class.insert({"班级":" 1 班"})

WriteResult({"nInserted" : 1 })
> db.class.update(... {"班级":" 1 班"},
... {"$push":{"students":{... "$each":["zs","ls","ww"],
... "$slice":-5}}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.class.findOne()
{"_id" : ObjectId("5854b5a0e7d717fcb974637b"),
        "班级" : " 1 班",
        "students" : ["zs",
                "ls",
                "ww"
        ]
}

> db.class.update(...  {"班级":" 1 班"},
...  {"$push":{"students":{... "$each":["yyb","rhr","www","qqq","eee","rrr"],
... "$slice":-5}}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.class.findOne()
{"_id" : ObjectId("5854b5a0e7d717fcb974637b"),
        "班级" : " 1 班",
        "students" : ["rhr",
                "www",
                "qqq",
                "eee",
                "rrr"
        ]
}
>

也可以在清理元素之前使用$sort,只要向数组中添加子对象就需清理,先排序后保留指定的个数。

> db.class.update({},{ "班级 " : " 一班"})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
>  db.class.update(...  {"班级 ":" 一班"},
... {"$push":{"students":
...  {"$each":[{"name":"aaa","age":1},{"name":"bbb","age":2},{"name":"ccc","age":3}],
...  "$slice":-2,
...  "$sort":{"age":-1}}}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.class.findOne()
{"_id" : ObjectId("5854b5a0e7d717fcb974637b"),
        "班级 " : " 一班",
        "students" : [
                {"name" : "bbb",
                        "age" : 2
                },
                {"name" : "aaa",
                        "age" : 1
                }
        ]
}
>

$ne 与 $addToSet

如果想将数组作为数据集使用,保证数组内的元素不会重复。可以在查询文档中用 $ne 或者 $addToSet 来实现。有些情况 $ne 根本行不通,有些时候更适合用$addToSet

> db.papers.insert({"authors cited":["yyb"]})
WriteResult({"nInserted" : 1 })
> db.papers.update({"authors cited":{"$ne":"Richie"}}, {"$push":{"authors cited":"Richie"}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.papers.findOne()
{"_id" : ObjectId("5854c900e7d717fcb974637e"),
        "authors cited" : ["yyb",
                "Richie"
        ]
}
> db.papers.update({"authors cited":{"$ne":"Richie"}}, {"$push":{"authors cited":"Richie"}})
WriteResult({"nMatched" : 0, "nUpserted" : 0, "nModified" : 0 })
>
 
 
 db.user.findOne()
{"_id" : ObjectId("5854cb40e7d717fcb974637f"),
        "username" : "joe",
        "emails" : ["joe@example.com",
                "joe@gmail.com",
                "joe@yahoo.com"
        ]
}

> db.user.update(... ... {"username":"joe"},
... ... {"$addToSet":{"emails":"joe@gmail.com"}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })
>  db.user.update(... ... ... {"username":"joe"},
... ... ... {"$addToSet":{"emails":"joe@hotmail.com"}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.user.findOne()
{"_id" : ObjectId("5854cb40e7d717fcb974637f"),
        "username" : "joe",
        "emails" : ["joe@example.com",
                "joe@gmail.com",
                "joe@yahoo.com",
                "joe@hotmail.com"
        ]
}
>

$addToSet$each组合起来,可以添加多个不同的值。而用 $ne$push组合就不能实现。

$addToSet 与 $push 的区别:前者添加到数组中时会去重,后者不会。

>db.user.insert({"username" : "joe"})
>  db.user.update(... {"username" : "joe"},
... 
... {"$addToSet":
...  {"emails" :{"$each": [... "joe@example.com",
...  "joe@gmail.com",
... "joe@yahoo.com",
... "joe@hotmail.com",
...  "joe@hotmail.com"]}}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.user.findOne()
{"_id" : ObjectId("5854ce5ce7d717fcb9746380"),
        "username" : "joe",
        "emails" : ["joe@example.com",
                "joe@gmail.com",
                "joe@yahoo.com",
                "joe@hotmail.com"
        ]
}
>

从数组中删除元素

$pop

可以从数组任何一端删除元素。

{“$pop”:{“key”:1}}从数组末尾删除一个元素

{“$pop”:{“key”:-1}}从数组头部删除一个元素

> db.class.findOne()
{"_id" : ObjectId("5854b5a0e7d717fcb974637b"),
        "班级 " : " 一班",
        "students" : [
                {"name" : "bbb",
                        "age" : 2
                },
                {"name" : "aaa",
                        "age" : 1
                }
        ]
}
> db.class.update(... {"班级 " : " 一班"},
... {"$pop":{"students":1}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.class.findOne()
{"_id" : ObjectId("5854b5a0e7d717fcb974637b"),
        "班级 " : " 一班",
        "students" : [
                {"name" : "bbb",
                        "age" : 2
                }
        ]
}
>

$pull

有时需要基于特定条件来删除,而不仅仅是依据元素位置,这时可以使用 $pull。$pull 会将所有匹配的文档删除,而不是只删除一个。

> db.list.insert(... {"todo":["dishes","laundry","dry cleaning"]})
WriteResult({"nInserted" : 1 })
> db.list.update({},{"$pull":{"todo":"laundry"}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.list.findOne()
{"_id" : ObjectId("585690afc5b0525a48a441b4"),
        "todo" : ["dishes",
                "dry cleaning"
        ]
}
>

数组操作符只能用于包含数组值的键。例如:不能将一个整数插入数组,也不能将一个字符串从数组中弹出。要修改标量值,使用 $set$inc。

基于位置的数组修改器

有两种方法操作数组中的值:通过位置或者定位操作符($)

位置

通过数组位置来操作。数组都是以 0 开头的,可以将下标直接作为键来选择元素。

> db.blog.insert(
... {...     "content": "...",
...     "comments": [
...         {...             "comment": "good post",
...             "author": "john",
...             "votes": 0
...         },
...         {...             "comment": "i thought it was too short",
...             "author": "claire",
...             "votes": 3
...         },
...         {...             "comment": "free watches",
...             "author": "alice",
...             "votes": -1
...         }
...     ]
... })
WriteResult({"nInserted" : 1 })

> db.blog.update({"content":"..."},{"$inc":{"comments.0.votes":1}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.findOne()
{"_id" : ObjectId("585694e4c5b0525a48a441b5"),
        "content" : "...",
        "comments" : [
                {"comment" : "good post",
                        "author" : "john",
                        "votes" : 1
                },
                {"comment" : "i thought it was too short",
                        "author" : "claire",
                        "votes" : 3
                },
                {"comment" : "free watches",
                        "author" : "alice",
                        "votes" : -1
                }
        ]
}
>

定位操作符 $

如果无法知道要修改的数组的下标,可以使用定位操作符 $, 用来定位查询文档已经匹配的元素,并进行更新。

> db.blog.update(... {"comments.author":"john"},
... {"$set":{"comments.$.author":"jim"}})
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.findOne()
{"_id" : ObjectId("585694e4c5b0525a48a441b5"),
        "content" : "...",
        "comments" : [
                {"comment" : "good post",
                        "author" : "jim",
                        "votes" : 1
                },
                {"comment" : "i thought it was too short",
                        "author" : "claire",
                        "votes" : 3
                },
                {"comment" : "free watches",
                        "author" : "alice",
                        "votes" : -1
                }
        ]
}
>

upsert

upsertupdate() 的第三个参数。表示没有则创建,有则正常更新。

> db.analytics.update({"url":"/blog"},{"$inc":{"pageviews":1}},true)
WriteResult({"nMatched" : 0,
        "nUpserted" : 1,
        "nModified" : 0,
        "_id" : ObjectId("58569d3cb6687ca8dfad4e01")
})
> db.analytics.findOne()
{"_id" : ObjectId("58569d3cb6687ca8dfad4e01"),
        "url" : "/blog",
        "pageviews" : 1
}
> db.analytics.update({"url":"/blog"},{"$inc":{"pageviews":1}},true)
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
>  db.analytics.findOne()
{"_id" : ObjectId("58569d3cb6687ca8dfad4e01"),
        "url" : "/blog",
        "pageviews" : 2
}
>

$setOnInsert

在创建文档的同时创建字段并为它赋值,但是在之后的所有更新操作中在,这个字段的值都不在改变。

$setOnInsert只会在文档插入时设置字段的值。

在预置或者初始化计数器时,或者对于不使用 ObjectIds 的集合来说,“$setOnInsert”是非常有用的。

> db.user.update({},{"$setOnInsert":{"createAt":new Date()}},true)
WriteResult({"nMatched" : 0,
        "nUpserted" : 1,
        "nModified" : 0,
        "_id" : ObjectId("58569fe1b6687ca8dfad4e02")
})
> db.user.findOne()
{"_id" : ObjectId("58569fe1b6687ca8dfad4e02"),
        "createAt" : ISODate("2016-12-18T14:40:33.273Z")
}
> db.user.update({},{"$setOnInsert":{"createAt":new Date()}},true)
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })
> db.user.findOne()
{"_id" : ObjectId("58569fe1b6687ca8dfad4e02"),
        "createAt" : ISODate("2016-12-18T14:40:33.273Z")
}
>

save

一个 shell 函数,不存在创建,反之则更新文档。

它只有一个参数:文档。要是这个文档含有“_id”键,save会调用 upsert。否则会调用insert。在shell 中快速对文档进行修改。

> db.user.save({"x":10,"y":20})
WriteResult({"nInserted" : 1 })
> var x=db.user.findOne()
> x.num=43
43
> db.user.save(x)
WriteResult({"nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.user.findOne()
{"_id" : ObjectId("5856a230c5b0525a48a441be"),
        "x" : 10,
        "y" : 20,
        "num" : 43
}

更新多个文档

默认情况下,只会更新匹配的第一个文档。要更新多个文档,需要将 update 的第 4 个参数设置为true

想要知道多文档更新到底更新了多少文档,可以运行 getLastError 命令。键 n 的值就是被更新文档的数量。

> db.coll.find()
{"_id" : ObjectId("5856994bc5b0525a48a441b9"), "x" : "a" }
{"_id" : ObjectId("58569966c5b0525a48a441ba"), "x" : "bbb" }
{"_id" : ObjectId("5856996fc5b0525a48a441bb"), "x" : "c" }
> db.coll.update({},{"$set":{"x":10}},false,true )
WriteResult({"nMatched" : 3, "nUpserted" : 0, "nModified" : 3 })
> db.runCommand({getLastError:1})
{"connectionId" : 1,
        "updatedExisting" : true,
        "n" : 3,
        "syncMillis" : 0,
        "writtenTo" : null,
        "err" : null,
        "ok" : 1
}
>

本文永久更新链接地址:http://www.linuxidc.com/Linux/2016-12/138257.htm

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-22发表,共计34422字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中