Mongodb操作手册
权限控制
用户概念
Mongodb的用户是由 用户名+所属库名组成
例如:
登录mongo testdb1 ,创建用户testuser
登录mongo testdb2 ,创建用户testuser
那上面创建的用户分别是:testuser@testdb1,testuser@testdb2
也就是说在哪个库下面创建用户,这个用户就是哪个库的
内建角色
Mongodb的授权采用了角色授权的方法,每个角色包括一组权限。
Mongodb已经定义好了的角色叫内建角色,我们也可以自定义角色。
这儿主要介绍内建角色,Mongodb内建角色包括下面几类:
1. 数据库用户角色:read、readWrite;
2. 数据库管理角色:dbAdmin、dbOwner、userAdmin;
3. 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager;
4. 备份恢复角色:backup、restore;
5. 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase
6. 超级用户角色:root
这里还有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、userAdminAnyDatabase)
7. 内部角色:__system
角色权限
数据库管理角色:
read:该角色只有读取数据的权限。只需读权限可以备份数据库
readWrite:该角色有数据的读写权限,但不能创建删除索引,也不能创建和删除数据库。读写权限可以还原数据库备份
dbAdmin:该角色拥有数据库管理权限(比如更改数据库类型)
dbOwner:是read、readWrite、dbAdmin这三者角色的集合体
userAdmin:该角色可以对其他角色权限进行管理
集群管理角色:
clusterAdmin:提供了最大的集群管理功能。相当于clusterManager,clusterMonitor,
and hostManager和dropDatabase的权限组合
clusterManager:提供了集群和复制集管理和监控操作。拥有该权限的用户可以操作config和local数据库(即分片和复制功能)
clusterMonitor:仅仅监控集群和复制集
hostManager:提供了监控和管理服务器的权限,包括shutdown节点,logrotate, repairDatabase等。
所有数据库角色:
readAnyDatabase:具有read每一个数据库权限。但是不包括应用到集群中的数据库。
readWriteAnyDatabase:具有readWrite每一个数据库权限。但是不包括应用到集群中的数据库。
userAdminAnyDatabase:具有userAdmin每一个数据库权限,但是不包括应用到集群中的数据库。
dbAdminAnyDatabase:提供了dbAdmin每一个数据库权限,但是不包括应用到集群中的数据库。
超级管理员权限:
root: dbadmin到admin数据库、useradmin到admin数据库以及UserAdminAnyDatabase。
但它不具有备份恢复、直接操作system.*集合的权限,但是拥有root权限的超级用户
可以自己给自己赋予这些权限。
开启授权机制
1、找到mongodb配置文件,设置noauth=true
重启Mongodb后,登录admin账号,创建一个超级权限用户
语法:
use admin db.createUser({user:'root',pwd:'root',roles:[{ "role" : "root", "db" : "admin" }]}); |
2、关闭mongodb
3、启用认证参数
要保证权限认证生效,需要在mongodb配置文件中加入auth=true,同时删掉noauth=true
4、启动Mongodb
5、认证登录
(1)在连接期间进行身份验证:
mongo 127.0.0.1:27017/db -u username -p passwordxxx |
(2)连接后验证
切换到身份验证数据库(在这里为test),并使用db.auth(<username>,<pwd>)方法进行身份验证:
use test db.auth("myTester", "xyz123" ) |
用户授权详解
1、创建用户并授权
语法:
db.createUser({user:"UserName",pwd:"Password",roles:[{role:"RoleName",db:"Target_DBName"}]}) |
首先选择在哪个库创建用户,如test:use test;
创建用户有3项需要提供:用户名,密码,角色列表
例如我要在test下面创建用testuser,密码为testpwd,角色列表包括test库的readWrite角色和userAdmin角色:
db.createUser({user:"testuser",pwd:"testpwd",roles:[{role:"readWrite",db:"test"},{role:"userAdmin",db:"test"}]}) |
用户登录:db.auth(‘testuser’,’testpwd’)
2、修改密码
首先进入目标库:
use test db.changeUserPassword('testuser','testPWD'); |
3、添加角色
首先进入目标库:
use test db.grantRolesToUser( "testuser", [ { role: "read",db:"admin"} ] ) |
4、回收角色权限
首先进入目标库:
use test db.revokeRolesFromUser("testuser",[ { role: "read",db:"admin"} ] ) |
5、删除用户
首先进入目标库:
use test db.dropUser("testuser") |
6、注意
在该库下创建的帐号,不能直接在其他库验证,只能在帐号创建库下认证,再去其他库进行操作。
命名规范
文档
文档就是键值对的一个有序集。例如:{“greeting”:”hello, world! ”,“foo”:“3”},其中“greeting”和“foo”是键,“hello,world!”和“3”是他们对应的值。
文档的键是字符串。除了少数例外情况,键可以使用任意UTF-8字符。
1、键不能含有\0(空字符)。这个字符用于表示键的结尾。
2、.和$具有特殊意义,只能在特定环境下使用。通常这两个字符是被保留的;如果使用不当的话,驱动程序会有提示。
3、MongoDB的文档不能有重复的键,且区分类型和大小写。
4、文档中的键/值对是有序的:{“x”:1,“y”:2}与{“y”:2,“x”:1}是不同的。
集合
集合就是一组文档。如果将MongoDB的一个文档比喻为关系型数据库中的一行,那么一个集合就相当于一张表。例如,下面两个文档可以存储在同一个集合里面:
{“greeting”:“hello,world!”} {“foo”:5 } |
集合使用名称进行标识。集合名可以是满足下列条件的任意UTF-8字符串。
1、集合名不能是空字符串(””)。
2、集合名不能包含\0字符(空字符),这个字符表示集合名的结束。
3、集合名不能以“system.”开头,这是为系统集合保留的前缀。例如,system.users这个集合保存着数据库的用户信息,而system.namespaces集合保存着所有数据库集合的信息。
4、用户创建的集合不能在集合名中包含保留字符‘$’。因为某些系统生成的集合中包含$,很多驱动程序确实支持在集合名里包含该字符。除非你要访问这种系统创建的集合,否则不应该在集合命中包含$。
子集
MongoDB还有一个子集合的概念,就是一个集合包含几个集合,这样也是便于管理,比如将people的基本信息和国籍信息这样放在一个people集合中有点勉强,可以直接写成一个文档放进去,也可以用两个子集合,一个表示基本信息baseinfo,一个表示国籍信息countryinfo。
数据库
在MongoDB中,多个文档组成集合,而多个集合可以组成数据库。
数据库通过名称来标识,这点预计和类似。数据库名可以是满足以下条件的任意UTF-8字符串:
1、不能是空字符串(“”)。
2、不得含有“/”、“\”、”、*、“<”、“>”、“:”、“|”、“?”、$(一个空格)、\0(空字符)。基本上,只能使用ASCII中的字母和数字。
3、数据库名区分大小写,即便是在不区分大小写的文件系统中也是如此。简单起见,数据库名迎全部小写。
4、数据库名最多为64字节。
5、保留数据库名有:admin、local、config。
SHELL中的基本操作
在shell中查看或操作数据会用到4个基本操作:创建、读取、更新和删除(即CRUD操作)。
创建
Insert函数可将一个文档添加到集合中。举一个存储博客文章的例子。首先,创建一个名为post的局部变量,这是一个JavaScript对象,用于表示我们的文档。它会有几个键:“title”、“content”和“date”(发布日期)。
这个对象是个有效的MongoDB文档,所以可以用insert方法将其保存到blog集合中:
也可这样插入:
db.blog.insert({“title”:“my blog post”,“content”:“test”,“date”:“2017-10-26”}) |
这篇文章已被存到数据库中。要查看它可用调用集合的find方法:
>db.blog.find()
读取
Find和findOne方法可以用于查询集合里的文档。若只想查看一个文档,可用findOne:
Find和findOne可以接受一个查询文档作为限定条件。这样就可以查询符合一定条件的文档。使用find时,shell会自动显示最多20个匹配的文档,也可获取更多文档。后续介绍。
更新
使用update修改博客文章。Update接受至少 两个参数:第一个是限定条件(用于匹配待更新的文档),第二个是新的文档。假设我们要给先前写的文章增加评论功能,就需要增加一个新的键,用于保存评论数组。
首先,修改变量post,增加“comments”键:
然后执行update操作,用新版本的文档替换标题为“My Blog Post”的文章:
现在,文档已经有了“comments”键。再用find查看一下,可以看到新的键:
删除
使用remove方法可将文档从数据库中永久删除。如果没有使用任何参数,它会将集合内的所有文档全部删除。它可以接受一个作为限定条件的文档作为参数。例如,下面的命令会删除刚刚创建的文章:
现在,集合又是空的了。
数据类型
MongoDB有着非常丰富的数据类型,如上面的name字段是字符串,age字段是数字,当然不止这两种。JSON只有6中数据类型,null,布尔,数字,字符串,数组和对象。MongoDB在JSON的基础上添加了一些其他的数据类型,下面来看看MongoDB的数据类型:
null:用于表示控制或者不存在的字段,如:{"x": null}。
布尔:只有两个值true和false。
32位整数:shell中这个类型不可用,javascript仅支持64位浮点数,所以32位整数会被自动转换。
64位整数:shell中这个类型不可用,shell会使用一个特殊的内嵌文档来显示64位整数。
64位浮点数:shell中的数字都是这个类型,{"x": 3.14}和{"x" : 3}都是浮点数。
因为javascript只有一种数字类型就是64位浮点型,所以MongoDB中从shell的来的数字都被当做64位浮点型,而MongoDB中支持三种数字类型,所以用shell修改过数据库中的数据后都会被转换成64位浮点型。64位整数并不能精确的表示64位浮点型,如果MongoDB中存入了一个64位整数,在shell中查看时,如果能够表示64位浮点型那就用一个键的内置文档显示而且这个值是精确的,否则,他会显示一个多键内嵌文档,表示可能不精确。
如果是64位整数3,那么在shell中查询显示会是这个样子:
db.nums.findOne() { "_id" : ObjectId("4c0beecfd096a2580fe6fa08"), "myInteger" : { "floatApprox" : 3 } } |
如果是64位整数9223372036854775807,那么在shell中查询显示会是这个样子:
db.nums.findOne() {undefined "_id" : ObjectId("4c0beecfd096a2580fe6fa08"), "myInteger" : {undefined "floatApprox" : 9223372036854775807, "top" : 2147483647, "bottom" : 4294967295 } } |
top和bottom分别表示高32位和低32位。
字符串:UTF-8字符串都可表示为字符串类型的数据,如:{"name" : "Mary"}。
符号:shell不支持这种类型,shell会将数据库中的符号类型转换成字符串。
对象id:对象id是文档的12字节的唯一ID。
MongoDB中存储的文档必须有一个键"_id",这个键可以是任意类型的,默认是ObjectId对象,当我们存入文档时不指定该键,那么MongoDB会自动添加这样一个键值对,这个值是唯一标识,ObjectId使用12字节的存储空间。
日期:日期类型存储的是从标准纪元开始的毫秒数,不存储时区,如:{"x": new Date()}。
javascript中Date对象用作MongoDB的日期类型,创建日期对象要用new Date()而不是Date(),返回的是对日期的字符串表示,而不是真正的Date对象。
正则表达式:文档中可以包含正则表达式,采用javascript的正则表达式语法,如:{"x" : /foobar/i}。
代码:文档中还可以包含javascript代码,如:{"x" : function(){/*...*/}}。
二进制数据:二进制数据可以由任意字节的串组成,不过shell中无法使用。
最大值:BSON包括一个特殊类型,表示可能的最大值,shell中没有这个类型。
最小值:BSON包括一个特殊类型,表示可能的最小值,shell中没有这个类型。
未定义:文档中也可以使用未定义类型,如:{"x" :undefined}
数组:值的集合或者列表可以表示成数组,数组中的元素可以是不同类型的数据,如:{"x": ["a", "b", "c", 20]}。
内嵌文档:文档可以包含别的文档,也可以作为值嵌入到父文档中,如:{"x": {"foo" : "bar"}}。
在我目前用的情况,布尔,数字,字符串,日期,数组和内嵌文档是用的最多的。
增删改查
添加数据
insert添加时如果主键重复则报错,而save添加时主键重复则覆盖
db.cname.insert({name:"zhangsan", age:23}) db.cname.save({name:"zhangsan", age:23}) |
删除数据
删除数据比较简单:
1.带条件删除
>db.user.remove({"name":"zhangshan"}); |
2.删除所有数据
>db.user.remove({}) |
3.删除集合
>db.user.drop() |
4.删除整个数据库
>show dbs; >db.user.getDB() >db.dropDatabase() |
删除文档是永久性的,不能撤销,也不能恢复的。因此,在执行remove()函数前先用find()命令来查看下是否正确。
mongodb删除集合后磁盘空间不释放,用db.repairDatabase()去修复才能释放。但是在修复的过程中如果出现了非正常的mongodb的挂掉,再次启动时启动不了的,需要先修复才可以,可以利用./mongod--repair --dbpath=/data/mongo/,如果你是把数据库单独的放在一个文件夹中指定dbpath时就指向要修复的数据库就可以,修复可能要花费很长的时间,在使用db.repairDatabase()去修复时一定要停掉读写,并且mongodb要有备机才可以,不然千万不要随便使用db.repairDatabase()来修复数据库,切记。
修改数据
1). update()
db.collection.update( criteria, objNew,upsert, multi )四个参数的说明如下:
criteria: update的查询条件,类似sql update查询内where后面的
objNew: update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql update查询内set后面的
upsert: 这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
multi: mongodb默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
几个查询例子如下:
db.mytest.update({count:{$gt:1}},{$set:{name:"ok"}}) |
只更新第一条记录
db.mytest.update({count:{$gt:3}},{$set:{name:"ok"}},false,true) |
大于3的全部更新了
db.mytest.update({count:{$gt:4}},{$set:{name:"ok123"}},true,false) |
只更新了一条
db.mytest.update({count:{$gt:6}},{$set:{name:"ok123"}},true,true) |
大于6的全部更新了
2). save()
db.collection.save(x): x是要插入的对象,效果与上面的insert命令一样。save与insert的区别是这样的:
在进行插入数据的操作中,当遇到_id相同的情况下,save完成保存操作,insert则会保存;即_id相同情况下,save相当于更新操作。
3). $inc
用法:{$inc:{field:value}} 意思是对一个数字字段field增加value:
[plain] > db.mytest.find({_id:1}) { "_id" : 1, "name" : "test1", "count" : 1 } > db.mytest.update({_id:1},{$inc:{count:1}}) > db.mytest.find({_id:1}) { "_id" : 1, "name" : "test1", "count" : 2 } //count字段加1 value的值也可以为负,就相当于减一个值: [plain] > db.mytest.update({_id:1},{$inc:{count:-2}}) > db.mytest.find({_id:1}) { "_id" : 1, "name" : "test1", "count" : 0 } //值从2减到0 |
4). $set命令
用法:{$set:{field:value}}
相当于在关系型数据库中sql的setfield=value,全部数据类型都支持$set操作
[plain] > db.mytest.update({_id:1},{$set:{count:111}}) > db.mytest.find({_id:1}) { "_id" : 1, "name" : "test1", "count" : 111 } //修改数值型 > db.mytest.update({_id:1},{$set:{name:"MongoDB"}}) > db.mytest.find({_id:1}) { "_id" : 1, "count" : 111, "name" : "MongoDB" } //修改字符型 |
5). $unset
用法:{$unset:{field:1}}
> db.mytest.find({_id:1}) { "_id" : 1, "count" : 111, "name" : "MongoDB" } > db.mytest.update({_id:1},{$unset:{name:1}}) > db.mytest.find({_id:1}) { "_id" : 1, "count" : 111 } //删除了字段name |
6).$push
用法:{$push:{field:value}}
把value追加到field中取,field一定是数据类型才行,如果field不存在,会新增一个数组类型加进去:
[plain]
>db.mytest.update({_id:15},{$set:{array:["aaa","bbb"]}})
> db.mytest.find({_id:15})
{ "_id" : 15,"array" : [ "aaa", "bbb" ],"count" : 15, "name" : "ok123" }
使用push追加数据:
[plain] > db.mytest.update({_id:15},{$push:{array:"ccc"}}) > db.mytest.find({_id:15}) { "_id" : 15, "array" : [ "aaa", "bbb", "ccc" ], "count" : 15, "name" : "ok123" } |
push一次只能追加一个值,如果需要追加多个值,则需要使用$pushAll:
[plain] > db.mytest.update({_id:15},{$pushAll:{array:["ddd","eee","fff"]}}) > db.mytest.find({_id:15}) { "_id" : 15, "array" : [ "aaa", "bbb", "ccc", "ddd", "eee", "fff" ], "count" : 15, "name" : "ok123" } |
7). $addToSet
用法:{$addToSet:{field:value}}
增加一个值到数组内,而且只有当这个值不在数组内才增加:
[plain] > db.mytest.update({_id:15},{$addToSet:{array:"123"}}) > db.mytest.find({_id:15}) { "_id" : 15, "array" : [ "aaa", "bbb", "123" ], "array2" : [ "mmm", "nnn"], "count" : 15, "name" : "ok123" } > db.mytest.update({_id:15},{$addToSet:{array:"aaa"}}) > db.mytest.find({_id:15}) { "_id" : 15, "array" : [ "aaa", "bbb", "123" ], "array2" : [ "mmm", "nnn"], "count" : 15, "name" : "ok123" } |
8). $pop
删除数组内的一个值,删除最后一个值:{$pop:{field:1}} ,删除第一个值:{$pop:{field:-1}}
[plain] > db.mytest.find({_id:15}) { "_id" : 15, "array" : [ "aaa", "bbb", "123" ], "array2" : [ "mmm", "nnn"], "count" : 15, "name" : "ok123" } > db.mytest.update({_id:15},{$pop:{array:1}}) > db.mytest.find({_id:15}) { "_id" : 15, "array" : [ "aaa", "bbb" ], "array2" : [ "mmm", "nnn" ], "count" : 15, "name" : "ok123" } |
9).$pull
用法:$pull:{field:value} 从数组中删除一个等于value的值:
[plain] > db.mytest.find({_id:15}) { "_id" : 15, "array" : [ "aaa", "bbb" ], "array2" : [ "mmm", "nnn" ], "coun t" : 15, "name" : "ok123" } > db.mytest.update({_id:15},{$pull:{array:"aaa"}}) > db.mytest.find({_id:15}) { "_id" : 15, "array" : [ "bbb" ], "array2" : [ "mmm", "nnn" ], "count" : 15, "name" : "ok123" } |
10).$pullAll
用法同$pull,可以一次删除数组内的多个值:
[plain] > db.mytest.find({_id:15}) { "_id" : 15, "array" : [ "bbb" ], "array2" : [ "mmm", "nnn" ], "count" : 15,"name" : "ok123" } > db.mytest.update({_id:15},{$pullAll:{array2:["mmm","nnn"]}}) > db.mytest.find({_id:15}) { "_id" : 15, "array" : [ "bbb" ], "array2" : [ ], "count" : 15, "name" : "ok123" } |
11). $
可以理解为数组定位器,看一个官方文档的例子:
[plain] > t.find() { "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 3 }, { "by" : "jane", "votes" : 7 } ] } > t.update( {'comments.by':'joe'}, {$inc:{'comments.$.votes':1}}) > t.find() { "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 7 } ] } |
需要注意的是,$只会找到第一条数组项,后面的就不管了:
[plain] > db.mytest.find({_id:16}) { "_id" : 16, "x" : [ 1, 2, 3, 1 ] } > db.mytest.update({x:1},{$inc:{"x.$":1}}) > db.mytest.find({_id:16}) { "_id" : 16, "x" : [ 2, 2, 3, 1 ] } |
还有一点需要注意,当$配合$unset使用的时候,会留下一个null的数组项,这个问题可以使用{$pull:{x:null}}解决:
[plain] > db.mytest.find({_id:16}) { "_id" : 16, "x" : [ 2, 2, 3, 1 ] } > db.mytest.update({x:3},{$unset:{"x.$":1}}) > db.mytest.find({_id:16}) { "_id" : 16, "x" : [ 2, 2, null, 1 ] } > db.mytest.update({_id:16},{$pull:{x:null}}) > db.mytest.find({_id:16}) { "_id" : 16, "x" : [ 2, 2, 1 ] } |
查询数据
第一种,mongodb的数据查询可以通过find()来查询,有几种常见的查询方式:
db.user.find() 查询出所有对象,这应该是最简单的全部查找的写法
第二种,使用游标来查询
var cursor=db.user.find(); while(cursor.hasNext()) printjson(cursor.next()); |
第三种,普通查询,游标可以看成为数组,可以查询数组上特定位置的值。
Var cursor = db.user.find(); While(cursor.hasNext()) printjson(cursor[4]) |
第四种,数组:
Var arr = db.user.find().toArray(); Printjson(arr[3]); |
条件数据查询
1,条件数据查询
就是在查询中加入过滤条件,mongodb的精确过滤条件是制定查询数据中json数据。 例如 db.user.find({“age”;”20”}) 相当于sql中的 select *from user where age = ‘20’
2,findOne()
mongodb为了减少游标的内存开销提供的方法,可以加过滤条件
. db.user.findOne() . db.user.findOne({“name”:”wpz”}) |
这两种写法都是合法的。
3,mongodb还提供limit来限制条数
. db.user.find().limit(2) . db.user.find({“name”:”wpz”}).limit(2) |
4,条件符查询
mongodb支持< <= > >= 四种运算符查询 db.user.find({“age”:{$gt:30}}) age大于30 db.user.find({“age”:{$lt:30}}) age小于30 db.user.find({“age”:{$gte:30}}) age大于或等于30 db.user.find({“age”:{$lte:30}}) age小于或等于30 |
多条件查询
db.user.find({“age”:{,gt:10,lte:30}}) |
5,匹配所有
$all 这个操作符号类似于sql中的in运算符,但是不同的是in只需要满足 一个值,但是alll需要满足所有值。
db.user.find({“age”:{$all:[6,8]}}); |
6,查询某一个字段是否存在:$exists
db.user.find({“password”:{$exists:true}}); password存在的记录 db.user.find({“password”:{$exists:false}}); password不存在的记录 |
7,null值得处理
null处理比较奇怪,因为mongodb中的数据集合不能指定特定的格式,没有sql中的字段的概念,就是说,在同一个集合中有的字段在一条数据中存在,在另一条数据中不存在,所以,要找出改字段是不是为空,先要判断这个字段是不是存在才行。
db.user.find({age:{“in":[null],"exists”:true}}); |
8,取模运算 $mod
查询所有age取模10之后为0 的数据,即查询age为10的倍数的字段:
db.user.find({age:{$mod:[10,0]}}); |
10,不等于 $ne –> (not equals)
查询所有age不等于10 的数据
db.user.find({age:{$ne:10}}); |
11,包含 $in
查询所有age等于10 或者20 的数据
db.user.find({age:{$in:[10,20]}}); |
12,不包含 $nin
查询所有age不等于10 或者20 的数据
db.user.find({age:{$nin:[10,20]}}); |
13,数组元素的个数 $size
查询age数据元素个数为3的数据
db.user.find({age:{$size:3}}); |
14,正则表达式匹配查询
name不以wpz开头的数据
db.user.find({“name”:{$not:/^wpz.*/}}); |
15,count查询条数
- db.user.find().count(); |
16,skip 设置查询数据的起点
查询从第三条数据之后的五条数据
- db.user.find().skip(3).limit(5); |
17,排序 sort
- db.user.find().sort({age:1}); 按照age升序 - db.user.find().sort({age:-1}); 按照age降序 |
18,极值操作符
下面的四个操作符可用于得到数据集合中的“边缘”值。
(1)“$max”:expr
返回组内的最大值
(2)“$min”:expr
返回组内的最小值
(1)“$first”:expr
返回分组的第一个值,忽略后面所有的值。只有排序之后,明确知道数据顺序时这个操作才有意义。
(1)“$last”:expr
与first相反,返回分组的最后一个值。
“$max”和“$min”会查看每一个文档,以便得到极值。因此,若数据是无序的,这两个操作符也可以有效工作;如果数据是有序的,这两个操作符就会有些浪费。假设有一个存有学生考试成绩的数据集,需要找到其中的最高分与最低分:
|
另一方面,如果数据集是按照希望的字段排序过,那么first和last更高效,且结果相同。
索引
索引的种类
1:_id索引:是绝大多数集合默认建立的索引,对于每个插入的数据,MongoDB都会自动生成一条唯一的_id字段
2:单键索引:是最普通的索引
与_id索引不同,单键索引不会自动创建 如:一条记录,形式为:{x:1,y:2,z:3}
db.imooc_2.getIndexes()//查看索引 db.imooc_2.ensureIndex({x:1})//创建索引,索引可以重复创建,若创建已经存在的索引,则会直接返回成功。 db.imooc_2.find()//查看数据 |
3:多键索引
多键索引与单键索引创建形式相同,区别在于字段的值。
1)单键索引:值为一个单一的值,如字符串,数字或日期。
2)多键索引:值具有多个记录,如数组。
db.imooc_2.insert({x:[1,2,3,4,5]})//插入一条数组数据 |
4:复合索引:查询多个条件时,建立复合索引
例如{x:1,y:2,z:3}这样一条数据,要按照x与y的值进行查询,就需要创建复合索引。
db.imooc_2.ensureIndex({x:1,y:1}) #1升序,-1降序 db.imooc_2.find({x:1,y:2}) #使用复合索引查询 |
5:过期索引
在一段时间后会过期的索引
在索引过期后,相应的数据会被删除
适合存储在一段时间之后会失效的数据,比如用户的登录信息、存储的日志等。
db.imooc_2.ensureIndex({time:1},{expireAfterSeconds:10}) #创建过期索引,time-字段,expireAfterSeconds在多少秒后过期,单位:秒 db.imooc_2.ensureIndex({time:1},{expireAfterSeconds:30}) #time索引30秒后失效 db.imooc_2.insert({time:new Date()}) #new Date()自动获取当前时间,ISODate db.imooc_2.find() #可看到刚才insert的值 |
过30秒后再find,刚才的数据就已经不存在了。
过期索引的限制:
(1)存储在过期索引字段的值必须是指定的时间类型,必须是ISODate或者ISODate数组,不能使用时间戳,否则不能自动删除。
例如 >db.imooc_2.insert({time:1}),这种是不能被自动删除的
(2)如果指定了ISODate数组,则按照最小的时间进行删除。
(3)过期索引不能是复合索引。因为不能指定两个过期时间。
(4)删除时间是不精确的。删除过程是由MongoDB的后台进程每60s跑一次的,而且删除也需要一定时间,所以存在误差。
6:全文索引:对字符串与字符串数组创建全文课搜索的索引 。
不适用全文索引:查找困难,效率低下,需要正则匹配,逐条扫描。
使用全文索引:简单查询即可查询需要的结果。
创建方式:
db.articles.ensureIndex({key:"text"}) #key-字段名,value-固定字符串text 上述指令表示,在articles这个集合的key字段上创建了一个全文索引 db.articles.ensureIndex({key1:"text",key2:"text"}) #在多个字段上创建全文索引 对于nosql数据库,每个记录存储的key可能都是不同的,如果要在所有的key上建立全文索引,一个一个写很麻烦,mongodb可以通过下面指令完成: db.articles.ensureIndex({"$**":"text"}) #给所有字段建立全文索引 |
全文索引的创建:
1:可以为一个字段创建全文索引
2:可以为多个字段创建全文索引
3:可以为集合中所有的字段创建全文索引
注意:上面三种创建全文索引的方式,前两个方式类似,第三个需要一个特殊的字符串来表示——”$**”,我想如果集合中就两三个字段,也可以使用2来创建这样的全文索引,如果这个集合总就一个字段使用1也是可以的,3仅仅是为了统一化而已。
全文索引的查找:
1:使用全文索引查询不需要指定全文索引的字段名字——直接使用$text,$search即可
2:在MongoDB中每个数据集合只允许创建一个全文索引,不过这个全文索引可以针对一个、多个、全部的数据集合的字段来创建。
3:查询多个关键词,可以使用空格将多个关键词分开——空格——或的关系
4:指定不包含的字段使用-来表示—— -:非的关系
5:引号包括起来代表与的关系—— \”\”:与的关系
db.articles.find({$text:{$search:"coffee"}}) db.articles.find({$text:{$search:"aa bb cc"}}) #空格代表或操作,aa或bb或cc db.articles.find({$text:{$search:"aa bb -cc"}}) #-号为非操作,即不包含cc的 db.articles.find({$text:{$search: "\"aa\" \"bb\" \"cc\""}}) #加双引号可以提供与关系操作 |
相似度查询:
搜索排序,查询结果与你查询条件越相关的越排在前面。
MongoDB中可以使用$meta操作符完成,格式:
{score:{$meta: "textScore"}} |
在全文搜索的格式中加入这样一个条件,如下:
db.imooc_2.find({$text:{$search:"aa bb"}},{score:{$meta:"textScore"}}) |
搜索出的结果会多出一个score字段,这个得分越高,相关度越高。
还可以对查询出的结果根据得分进行排序:
db.imooc_2.find({$text:{$search:"aabb"}},{score:{$meta:"textScore"}}).sort({score:{$meta:"textScore"}}) |
加上.sort方法即可。
全局索引的限制:
1.每次查询,只能指定一个$text查询
2.$text查询不能出现在$nor查询中
3. 查询中如果包含了$text, hint不再起作用(强制指定索引hint)
4. MongoDB全文索引还不支持中文
7:地理位置索引
将一些点的位置存储在MongoDB中,创建索引后,可以按照位置来查找其他点。
地理位置索引分为两类:
1.2D索引,用于存储和查找平面上的点。
2.2Dsphere索引,用于存储和查找球面上的点。
例如:
查找距离某个点一定距离内的点。
查找包含在某区域内的点。
分为2种:2D平面地理位置索引和 2D sphere 2D球面地里位置索引 2者的区别在于计算距离时使用的计算方式不同(平面距离还是球面距离)
2D地理位置索引创建方式
db.collection.ensureIndex({w:”2d”}) |
2D地理位置索引的取值范围以及表示方法经纬度[经度,纬度]
经纬度取值范围 经度[-180,180] 纬度[-90,90]
db.collection.insert({w:[180,90]}) |
2D地理位置查询有2种
1.使用$near 查询距离某个点最近的点 ,默认返回最近的100个点
db.collection.find({w:{$near:[x,y]}}) |
可以使用$maxDistance:x 限制返回的最远距离
db.collection.find({w:{$near:[x,y],$maxDistance:z}}) |
2.使用$geoWithin 查询某个形状内的点
形状的表示方式:
1. $box 矩形,使用{$box:[[x1,y1],[x2,y2]]} 2. $center 圆形,使用 {$center:[[x,y],r]} 3. $polygon 多边形,使用 {$polygon:[[x1,y1],[x2,y2],[x3,y3]]} |
mongodb geoWithin 查询
查询矩形中的点 db.collection.find({w:{$geoWithin:{$box:[[0,0],[3,3]]}}}) 查询圆中的点 db.collection.find({w:{$geoWithin:{$center:[[0,0],5]}}}) 查询多边形中的点 db.collection.find({w:{$geoWithin:{$polygon:[[0,0],[0,1],[2,5],[6,1]]}}}) |
mongodb geoNear 查询
geoNear 使用 runCommand命令操作 db.runCommand({undefined geoNear:"collection名称", near:[x, y], minDistance:10(对2d索引无效,对2Dsphere有效') maxDistance:10 num:1 返回数量 }) |
可返回最大距离和平均距离等数据.
返回的数据:
results:查询到的数据;dis:距离,obj:数据记录
stats:查询参数,maxDistance最大距离和avgDistance平均距离
ok:1,查询成功
mongodb 2Dsphere索引详解
2Dsphere index create method
use command:
db.collection.ensureindex({key: '2dsphere'}) |
2Dsphere位置表示方式:
GeoJSON:描述一个点,一条直线,多边形等形状。
格式:
{type:'', coordinates:[list]} |
GeoJSON查询可支持多边形交叉点等,支持MaxDistance 和 MinDistance
索引的属性
创建索引的格式:
db.collection.ensureIndex({indexValue},{indexProperty}) |
其中:indexProperty比较重要的有
1:名字
db.collection.ensureIndex({indexValue},{name:}) |
MongoDB会自动的创建,规则是key_1 或者 key_-1 1或者-1代表排序方向,一般影响不大,长度一般有限制125字节
为了见名知意我们可以自己来命名
自定义索引名称方式:
db.imooc_2.ensureIndex({x:1,y:1,z:1,m:1},{name:"normal_index"}) |
删除索引
db.imooc_dropIndex(indexName) |
删除索引时,可以通过我们定义的名字来删除索引
db.imooc_2.dropIndex("normal_index") |
2:唯一性:不允许在同一个集合上插入具有同一唯一性的数据。
db.imooc_2.ensureIndex({x:1,y:1,z:1,m:1},{unigue:true) |
3:稀疏性
db.collection.ensureIndex({},{sparse:true/false}) #指定索引是否稀疏 |
MongoDB索引默认是不稀疏的。
稀疏性的不同代表了MongoDB在处理索引中存在但是文档中不存在的字段的两种不同的方法。
例如,我们为一个collection的x字段指定了索引,但这个collection中可以插入如{y:1,z:1}这种不存在x字段的数据,
如果索引为不稀疏的,mongodb依然会为这个数据创建索引,如果在创建索引时指定为稀疏索引,那么就可以避免这件事情发生了。
db.imooc_2.insert({"m":1}) db.imooc_2.insert({"n":1}) |
通过$exists可以判断字段是否存在,如
db.imooc_2.find({m:{$exists:true}}) #筛选出有m字段的文档 |
给这个文档的m字段创建一个稀疏索引:
db.imooc_2.ensureIndex({m:1},{sparse:true}) |
第二条文档不存在m字段,所以不会创建这个索引
如果使用稀疏索引查找不存在稀疏索引字段的文档,mongodb则不会使用这个索引查找
例如:
db.imooc_2.find({m:{$exists:false}}) #可以查到数据 |
但如果我们通过hint强制使用索引,就不会查到数据了
db.imooc_2.find({m:{$exists:false}}).hint("m_1") #查不出数据,因为n上并没有m字段的索引 |
聚合
MongoDB中的聚合aggregate主要用于处理数据计算。
聚合框架
使用聚合框架可以对集合中的文档进行变换和组合。基本上,可以用多个构件创建一个管道(pipeline),用于对一连串的文档进行处理。这些构架包括筛选(filtering)、投射(projecting)、分组(grouping)、限制(limiting)和跳过(skipping)。
例如一个保存着动物类型的集合,希望找出最多的那种动物,假设每种动物被保存为一个mongodb文档,可以按照以下步骤创建管道。
1)将每个文档的动物名称映射出来。
2)安装名称排序,统计每个名称出现的次数。
3)将文档按照名称出现的次数降序排列。
4)将返回结果限制为前五个。
具体操作符:
1){"$porject",{"name" : 1}}
类似于查询阶段的字段选择器,指定"fieldname": 1选定需要的字段,"fieldname" : 0排除不需要的字段,"_id"字段自动显示。结果保存在内存中,不会写入磁盘。
db.test_collection.aggregate({"$project" : {"name" : 1}}); => { "_id" : ObjectId("535a2d3c169097010b92fdf6"), "name" : "snake" } |
2){"$group", {"_id" : "$name","count" : {"$sum" : 1}}}
首先指定了分组的字段"name",该操作执行完后,每个name只对应一个结果,所有可以将name指定为唯一标识符"_id"。第二个字段表明分组内的每个文档"count"字段加1。新加入的文档中不会有count字段。
db.test_collection.aggregate({"$project" : {"name" : 1}}, {"$group" : {"_id" : "$name", "count" : {"$sum" : 1}}}); => { "_id" : "bird", "count" : 8344 } { "_id" : "snake", "count" : 8443 } { "_id" : "cat", "count" : 8183 } { "_id" : "rabbit", "count" : 8206 } { "_id" : "tiger", "count" : 8329 } { "_id" : "cow", "count" : 8309 } { "_id" : "horse", "count" : 8379 } { "_id" : "dog", "count" : 8406 } { "_id" : "dragon", "count" : 8372 } { "_id" : "elephant", "count" : 8264 } { "_id" : "pig", "count" : 8403 } { "_id" : "lion", "count" : 8362 } |
3){"$sort" :{"count" : -1}}
对结果集中的文档根据count字段做降序排列。
4){"$limit" : 5}
将返回结果限制为5个文档。
将上述结果综合起来:
db.test_collection.aggregate( {undefined "$project" : {"name" : 1}}, {"$group" : {"_id" : "$name", "count" : {"$sum" : 1}}}, {"$sort" : {"count" : -1}}, {"$limit" : 5} ); |
aggregate会返回一个文档数组,内容为出现次数最多的5个动物:
{ "_id" : "snake", "count" : 8443 } { "_id" : "dog", "count" : 8406 } { "_id" : "pig", "count" : 8403 } { "_id" : "horse", "count" : 8379 } { "_id" : "dragon", "count" : 8372 } |
调试过程中。可以逐一对管道符进行排查。
聚合框架不能对集合进行写入操作,所有结果返回给客户端,聚合结果必须限制在16M以内。
管道操作符
每个操作符都会接受一连串的文档,对这些文档进行类型转换,最后得到的文档作为结果传递给下一操作符。
不同的管道操作符可以将任意顺序组合在一起使用,而且可以被重复任意多次。
1、 $match
$match用于对文档集合进行筛选,之后得到的文档子集做聚合。
"$match"支持所有的常规查询操作符("$gt","$lt","$ne")等,不能使用地理空间操作符。
实际操作中尽量将"$match"放在管道的前面部分,一方面可以提快速将不需要的文档过滤掉,另外在映射和分组前筛选,查询可以使用索引。
2、 $project
使用"$project"可以提取字段,可以重命名字段,
db.foo.aggregate({"$project" : {"city" : 1, "_id" : 0}}) => { "city" : "NEW WORK" } |
可以将投射过的字段重命名:
db.foo.aggregate({"$project" : {"newcity" : "$city", "_id" : 0}}) => { "newcity" : "NEW WORK" } |
使用"$fieldname"语法为了在聚合框架中引用fieldname字段,例如上面"$city"会被替换为"NEW WORK"。
对字段重命名后,Mongdb不会记录其记录字段的历史名称,所以应该在修改字段名称前使用索引。
数学表达式
数学表示式用来操作数据运算。
db.foo.aggregate( {"$project" : {"total" : {"$add" : ["$age", "$year"]}, "_id" : 0 } } ) {"total" : 15} |
可以将多个表达式组合为更为复杂的表达式:
db.foo.aggregate( {"$project" : {"sub" : {"$subtract" : [{"$add" : ["$age", "$year"]}, 7]}, "_id" : 0 } } ) { "sub" : 8 } |
操作符语法:
1)将表达式相加:"$add" : [expr1,[, expr2, ..., exprN]]
2)达式1减去表达式2:"$subtract": [expr1, expr2]
3)将表达式相乘:"$multiply" :[expr1, [, expr2, ..., exprN]]
4)表达式1除以表达式2得到商:"$divide": [expr1, expr2]
5)表达式1除以表达式2得到余数:"$mod": [expr1, expr2]
日期表达式
用于提取日期信息的表达式:"$year","$month","$week","$dayOfMonth","$dayOfweek","$hour","$minute","$second"。只能对日期类型的字段进行日期操作,不能对数值类型进行日期操作。
db.bar.insert({"name" : "pipi", "date" : new Date()}) db.bar.aggregate( {"$project" : {"birth-month" : {"$month" : "$date"}, "_id" : 0 } } ) { "birth-month" : 4 } |
也可以使用字面量日期。
db.bar.aggregate( {"$project" : {"up-to-now" : {"$subtract" : [{"$minute" : new Date()}, {"$minute" : "$date"}]}, "_id" : 0 } } ) { "up-to-now" : 18 } |
字符串表达式
操作符语法:
1)"$substr": [expr,startOffset, numoReturn]
接受字符串,起始位置以后偏移N个字节,截取字符串。
2)"$concat": [expr1[,expr2, ..., exprN]]
将给定的表达式连接在一起作为返回结果。
3)"$toLower": expr
返回参数的小写形式
4)"$toUpper": expr
返回参数的大写形式
例如:
db.foo.insert({"firstname" : "caoqing", "lastname" : "lucifer"}) db.foo.aggregate( {undefined "$project" : {undefined "email" : {undefined "$concat" : [ {"$substr" : ["$firstname", 0, 1]}, ".", "$lastname", "@gmail.com" ] }, "_id" : 0 } } ) { "email" : "c.lucifer@gmail.com" } |
逻辑表达式
操作符语法:
1)"$cmp": [expr1,expr2]
比较两个参数,相等返回0,大于返回整数,小于返回负数。
2)"$strcasecmp": [string1,string2]
比较字符串,区分大小写
3)"$eq"/"$ne"/"$gt"/"$gte"/"lt"/"lte": [expr1,expr2]
比较字符串,返回结果(true or false)
4)"$and": [expr1[,expr2, ..., exprN]]
所有值为true返回true,否则返回false。
5)"$or": [expr1[,expr2, ..., exprN]]
任意表达式为true返回true,否则返回false
6)"$not": expr
对表示式取反
还有两个控制语句:
"$cond": [booleanExpr,trueExpr, falseExpr]
如果为true,返回trueExpr,否则,返回falseExpr。
"$ifFull": [expr,replacementExpr]
如果expr为null,返回replacementExpr,否则返回expr。
通过这些操作符,就可以在聚合中使用更复杂的逻辑,可以对不同数据执行不同的代码,得到不同的结果。
管道对于输入数据的形式有特定要求,所以这些操作符在传入数据时要特别注意。算术操作符必须接受数值,日期操作符必须接受日期,字符串操作符必须接受字符串。如果有字符缺失,这些操作符就会报错。如果你的数据及不一致,可以通过这个条件来检测缺失的值,并且进行填充。
7)一个提取的例子
假如有个教授想通过某种比较复杂的计算为学生打分:出勤率占10%,平时作业占30%,考试成绩占60%(如果是老师宠爱的学生,那么分数就是100分)。可以使用如下代码:
分组去重
Distinct
Distinct用来找出给定键的所有不同值。使用时必须指定集合和键。
假设集合中有如下文档:
如果对“age”键使用distinct,会得到所有不同的年龄:
Group
使用group可以执行更复杂的聚合。先选定分组所依据的键,而后MongoDB就会降级和依据选定键的不同值分成若干组。然后可以对每一个分组内的文档进行聚合,得到一个结果文档。
插入示例数据:
var name = ["Caoqing", "Spider-man", "Garfield"] for (var i = 0; i < 10000; i++) {undefined iname = name[Math.floor(Math.random() * name.length)]; date = new Date().getTime(); number = Math.floor(100 * Math.random()); db.coll.insert({_id : i, name : iname, time : date, age : number}); } |
生成的列表中包含最新的时间和最新的时间对应的年纪。
可以安装name进行分组,然后取出每个分组中date最新的文档,将其加入结果集。
db.runCommand({"group" : {undefined "ns" : "coll", "key" : {"name" : true}, "initial" : {"time" : 0}, "$reduce" : function(doc, prev) {undefined if (doc.time > prev.time) {undefined prev.age = doc.age; prev.time = doc.time; } } }}) |
(1)"ns": "coll"
指定进行分组的集合。
(2)"key" :{"name" : true}
指定分组依据的键。
(3)"initial" :{"time" : 0}
初始化time值,作为初始Wednesday传递给后续过程。每组成员都会使用这个累加器。
如果有文档不存在指定分组的键,这些文档会单独分为一组,缺失的键会使用name:null这样的形式。如下:
db.coll.insert({age : 5, time : new Date().getTime()}) |
返回结果:
{undefined "name" : null, "time" : 1399180685288, "age" : 5 } "count" : 10001, "keys" : 4, ... |
为了排除不包含指定用于分组的键的文档,可以在"condition"中加入"name":{"$exists" : true}。
db.runCommand({"group" : {undefined "ns" : "coll", "key" : {"name" : true}, "initial" : {"time" : 0}, "$reduce" : function(doc, prev) {undefined if (doc.time > prev.time) {undefined prev.age = doc.age; prev.time = doc.time; } }, "condition" : {"name" : {"$exists" : true}} }}) |
使用完成器
完成器(finalizer)用于精简从数据库传到用户的数据,因为group命令的输出结果需要能够通过单次数据库响应返回给用户。
将函数作为键使用
分组条件可以非常复杂,不是单个键,例如分组时按照类别分组dog和DOG是两个完全不同的组,为了消除大小写差异,可以定义一个函数决定文档分组所依据的键。
定义分组函数需要用到"$keyf"键,
db.foo.group({undefined "ns" : "foo", "$keyf" : function(x) { return x.category.toLowerCase(); }; "initial" : ..., ...... }) |