MongoDB是一个基于分布式文件存储的数据库,由C++语言编写。
需要启用服务端才能使用。启用服务的命令是:Mongod,链接默认端口是27017。
sudo mkdir -p /data/db
exception in initAndListen: IllegalOperation: Attempted to create a lock file on a read-only directory: /data/db, terminating
chmod 777 -R data
服务端开启后,我们可以使用命令行来链接服务端,链接命令是mongo。
基本命令:
- show dbs : 显示已有数据库,如果你刚安装好,会默认有local、admin(config),这是MongoDB的默认数据库,我们在新建库时是不允许起这些名称的。
- use admin: 进入数据,也可以理解成为使用数据库。成功会显示:switched to db admin。
- show collections: 显示数据库中的集合(关系型中叫表,我们要逐渐熟悉)。
- db: 显示当前位置,也就是你当前使用的数据库名称,这个命令算是最常用的,因为你在作任何操作的时候都要先查看一下自己所在的库,以免造成操作错误。
- 查看数据库版本命令:db.version();
- use db(建立数据库):use不仅可以进入一个数据库,如果你敲入的库不存在,它还可以帮你建立一个库。但是在没有集合前,它还是默认为空。
- db.集合.insert( ): 新建数据集合和插入文件(数据),当集合没有时,这时候就可以新建一个集合,并向里边插入数据。Demo:db.user.insert({“name”:”jspang”}); 批量是以数组来存储的:db.user.insert([{},{}]。注意一次插入不要超过48M。
- db.集合.find( ): 查询所有数据,这条命令会列出集合下的所有数据,可以看到MongoDB是自动给我们加入了索引值的。Demo:db.user.find()
- db.集合.findOne( ): 查询第一个文件数据,这里需要注意的,所有MongoDB的组合单词都使用首字母小写的驼峰式写法。
- db.集合.update({查询},{修改}): 修改文件数据,第一个是查询条件,第二个是要修改成的值。这里注意的是可以多加文件数据项的,比如下面的例子。
-
db.users.update({"name":"zhangsan"},{"name":"lisi","age":"32"})
- db.集合.remove(条件):删除文件数据,注意的是要跟一个条件。Demo:db.user.remove({“name”:”jspang”})
- db.集合.drop( ): 删除整个集合,这个在实际工作中一定要谨慎使用,如果是程序,一定要二次确认。
- db.dropDatabase( ): 删除整个数据库,在删除库时,一定要先进入数据库,然后再删除。实际工作中这个基本不用,实际工作可定需要保留数据和痕迹的。
使用js文件写mongo命令:
test.js
var userName="zhangsan1"; //声明一个登录名
var timeStamp=Date.parse(new Date()); //声明登录时的时间戳
var jsonDdatabase={"loginUnser":userName,"loginTime":timeStamp}; //组成JSON字符串
var db = connect('log'); //链接数据库
db.login.insert(jsonDdatabase); //插入数据
print('[demo]log print success'); //没有错误显示成功
执行JS文件 mongo tes.js 插入成功。
update修改器:
$set修改器
用来修改一个指定的键值(key),这时候要修改sex和age就非常方便了。
db.workmate.update({"name":"zahngsan"},{"$set":{sex:2,age:21}})
修改好后,我们可以用db.workmate.find()来进行查看,会发现数据已经被修改。
修改嵌套内容(内嵌文档)
比如现在的UI的技能发生了变化,不会作PPT而是word作的很好,需要进行修改。skill数据是内嵌的,这时候我们可以属性的形式进行修改,skill.skillThree。具体看下面的代码。
db.workmate.update({"name":"zhangsan"},{"$set":{"skill.skillThree":'word'}})
这样就可以简单的修改内嵌文档了。
$unset用于将key删除
它的作用其实就是删除一个key值和键。一般女孩子都是不希望看到自己的年龄的,所以要求我们把年龄删除。这时候我们就可以使用$unset的形式。
db.workmate.update({"name":"zhangsan"},{$unset:{"age":''}})
当你删除后,想加回来可以直接用set进行添加。
$inc对数字进行计算
它是对value值的修改,但是修改的必须是数字,字符串是不起效果的。我们现在要对zhangsan的年龄减去2岁,就可以直接用$inc来操作。
db.workmate.update({"name":"zhangsan"},{$inc:{"age":-2}})
multi选项
现在领导说了,你要把每个人的爱好也加入进来,但是如果你直接写会只加一个,比如下面这种形式。
db.workmate.update({},{$set:{interset:[]}})
这时候你用db.workmate.find()查找,你会发现只改变了第一个数据,其他两条没有改变。这时候我们想改变就要用到multi选项。
db.workmate.update({},{$set:{interset:[]}},{multi:true})
这时候每个数据都发生了改变,multi是有ture和false两个值,true代表全部修改,false代表只修改一个(默认值)
upsert选项
upsert是在找不到值的情况下,直接插入这条数据。比如我们这时候又来了一个新同事lisi,我们这时候修改他的信息,age设置成20岁,但集合中并没有这条数据。这时候可以使用upsert选项直接添加。
db.workmate.update({name:'lisi'},{$set:{age:20}},{upsert:true})
upsert也有两个值:true代表没有就添加,false代表没有不添加(默认值)。
push选项
$push的功能是追加 数组 中的值,但我们也经常用它操作内嵌稳文档,就是{}对象型的值。先看一个追加数组值的方式,比如我们要给lisi加上一个爱好(interset)为画画(draw):
db.workmate.update({name:'lisi'},{$push:{interest:'draw'}})
当然$push修饰符还可以为内嵌文档增加值,比如我们现在要给我们的UI,增加一项新的技能skillFour为draw,这时候我们可以操作为:
db.workmate.update({name:'zhangsan'},{$push:{"skill.skillFour":'draw'}})
$ne查找是否存在
它主要的作用是,检查一个值是否存在,如果不存在再执行操作,存在就不执行,这个很容易弄反。例子:如果lisi的爱好(interest)里没有palyGame这个值,我们就加入Game这个爱好。
db.workmate.update({name:'lisi',"interest":{$ne:'playGame'}},{$push:{interest:'Game'}})
总结:没有则修改,有则不修改。
$addToSet 升级版的$ne
它是$ne的升级版本(查找是否存在,不存在就push上去),操作起来更直观和方便,所以再工作中这个要比$en用的多。
例子:我们现在要查看lis兴趣(interest)中有没有阅读(readBook)这项,没有则加入读书(readBook)的兴趣.
db.workmate.update({name:"lisi"},{$addToSet:{interest:"readBook"}})
$each 批量追加
它可以传入一个数组,一次增加多个值进去,相当于批量操作,性能同样比循环操作要好很多,要先组合成数组,然后用批量的形式进行操作。
例子:我们现在要给lisi,一次加入三个爱好,唱歌(Sing),跳舞(Dance),编码(Code)。
var newInterset=["Sing","Dance","Code"];
db.workmate.update({name:"lisi"},{$addToSet:{interest:{$each:newInterset}}})
$pop 删除数组值
$pop只删除一次,并不是删除所有数组中的值。而且它有两个选项,一个是1和-1。
- 1:从数组末端进行删除
- -1:从数组开端进行删除
例子:现在要删除lisi的编码爱好(code)。
db.workmate.update({name:'lisi'},{$pop:{interest:1}})
数组定位修改
有时候只知道修改数组的第几位,但并不知道是什么,这时候我们可以使用interest.int 的形式。
例子,比如我们现在要修改lisi的第三个兴趣为编码(Code),注意这里的计数是从0开始的。
db.workmate.update({name:'lisi'},{$set:{"interest.2":"Code"}})
findAndModify(应答式操作) --状态返回和安全
db.runCommand( ):
db.workmate.update({sex:1},{$set:{money:1000}},false,true)
var resultMessage=db.runCommand({getLastError:1})
printjson(resultMessage);
- false:第一句末尾的false是upsert的简写,代表没有此条数据时不增加;
- true:true是multi的简写;
- getLastError:1 :表示返回功能错误,这里的参数很多;
- printjson:表示以json对象的格式输出到控制台;
上边的代码,我们修改了所有男士的数据,每个人增加了1000元钱(money),然后用db.runCommand()执行,可以看到执行结果在控制台返回了。
{
"connectionId" : 1,
"updatedExisting" : true,
"n" : 2,
"syncMillis" : 0,
"writtenTo" : null,
"err" : null,
"ok" : 1
}
- db.listCommands( ):查看所有的Commad命令
比如我们要查看是否和数据库链接成功了,就可以使用Command命令。
db.runCommand({ping:1})
返回ok:1就代表链接正常。
findAndModify:
从名字上就可以看出,findAndModify是查找并修改的意思。配置它可以在修改后给我们返回修改的结果。我们先看下面的代码:
var myModify={
findAndModify:"workmate", // 集合
query:{name:'lisi'},
update:{$set:{age:18}},
new:true //更新完成,需要查看结果,如果为false不进行查看结果
}
var ResultMessage=db.runCommand(myModify);
printjson(ResultMessage)
findAndModify的性能是没有直接使用db.collections.update的性能好,但是在实际工作中都是使用它,毕竟要商用的程序安全性还是比较重要的。
findAndModify属性值:
- query:需要查询的条件/文档
- sort: 进行排序
- remove:[boolean]是否删除查找到的文档,值填写true,可以删除, 和update互斥存在。
- new:[boolean]返回更新前的文档还是更新后的文档。
- fields:需要返回的字段
- upsert:没有这个值是否增加。
find修饰符:
筛选字段
筛选字段需要写第二个参数,看下面的代码。
db.workmate.find(
{"skill.skillOne":"HTML+CSS"},
{name:true,"skill.skillOne":true}
)
你会在终端中看到如下结果:
{ "_id" : ObjectId("5a611350c4e36dee6008987b"), "name" : "ShengLei", "skill" : { "skillOne" : "HTML+CSS" } }
{ "_id" : ObjectId("5a611350c4e36dee6008987e"), "name" : "LiangPeng", "skill" : { "skillOne" : "HTML+CSS" } }
仔细观察查找到的数据中多了一个ID字段,这个也不是我们想要的,这时候只要把_id:false就可以了。当然这里的false和true,也可以用0和1表示。
db.workmate.find(
{"skill.skillOne":"HTML+CSS"},
{name:true,"skill.skillOne":true,_id:false}
)
不等修饰符
- 小于($lt):英文全称less-than
- 小于等于($lte):英文全称less-than-equal
- 大于($gt):英文全称greater-than
- 大于等于($gte):英文全称greater-than-equal
- 不等于($ne):英文全称not-equal 我们现在要查找一下,公司内年龄小于30大于25岁的人员。看下面的代码。
db.workmate.find(
{age:{$lte:30,$gte:25}},
{name:true,age:true,"skill.skillOne":true,_id:false}
)
日期查找
MongoDB也提供了方便的日期查找方法,现在我们要查找注册日期大于2018年1月1日的数据,我们可以这样写代码。
var startDate= new Date('01/01/2018');
db.workmate.find(
{regeditTime:{$gt:startDate}},
{name:true,age:true,"skill.skillOne":true,_id:false}
)
多条件查询find:
$in修饰符
in修饰符可以轻松解决一键多值的查询情况。就如上面我们讲的例子,现在要查询同事中年龄是25岁和33岁的信息。
db.workmate.find({age:{$in:[25,33]}},
{name:1,"skill.skillOne":1,age:1,_id:0}
)
于$in相对的修饰符是$nin,就是查询除了$in条件以为的指。
$or修饰符
它用来查询多个键值的情况,就比如查询同事中大于30岁或者会做PHP的信息。主要区别是两个Key值。$in修饰符是一个Key值,这个需要去比较记忆。
db.workmate.find({$or:[
{age:{$gte:30}},
{"skill.skillThree":'PHP'}
]},
{name:1,"skill.skillThree":1,age:1,_id:0}
)
or很好理解,就是或者的意思,我们查出来的结果也是一样的,查出了年龄大于30岁的,或者会做PHP的信息。相对应的还有$nor修饰符。
$and修饰符
$and用来查找几个key值都满足的情况,比如要查询同事中大于30岁并且会做PHP的信息,这时需要注意的是这两项必须全部满足。当然写法还是比较简单的。只要把上面代码中的or换成and就可以了。
db.workmate.find({$and:[
{age:{$gte:30}},
{"skill.skillThree":'PHP'}
]},
{name:1,"skill.skillThree":1,age:1,_id:0}
)
$not修饰符
它用来查询除条件之外的值,比如我们现在要查找除年龄小于20岁,大于30岁的人员信息。需要注意的是$not修饰符不能应用在条件语句中,只能在外边进行查询使用。
db.workmate.find({
age:{
$not:{
$lte:30,
$gte:20
}
}
},
{name:1,"skill.skillOne":1,age:1,_id:0}
)
find数组:
$all-数组多项查询
如果要查询出喜欢看电影和看书的人员信息,也就是对数组中的对象进行查询,这时候要用到一个新的查询修饰符$all。
db.workmate.find(
{interest:{$all:["看电影","看书"]}},
{name:1,interest:1,age:1,_id:0}
)
这时候找到了兴趣中既有看电影又有看书的人员。
$in-数组的或者查询
用$all修饰符,是需要满足所有条件的, (有时候会跟$or弄混)。比如现在要查询爱好中有看电影的或者看书的员工信息。
db.workmate.find(
{interest:{$in:["看电影","看书"]}},
{name:1,interest:1,age:1,_id:0}
)
$size-数组个数查询
$size修饰符可以根据数组的数量查询出结果。比如现在我们要查找兴趣的数量是5个人员信息,这时候就可以使用$size。
db.workmate.find(
{interest:{$size:5}},
{name:1,interest:1,age:1,_id:0}
)
这时候是5项爱好的人员就会显示出来了。
$slice-显示选项
有时候我并不需要显示出数组中的所有值,而是只显示前两项,比如我们现在想显示每个人兴趣的前两项,而不是把每个人所有的兴趣都显示出来。
db.workmate.find(
{},
{name:1,interest:{$slice:2},age:1,_id:0}
)
这时候就显示出了每个人兴趣的前两项,如果我们想显示兴趣的最后一项,可以直接使用slice:-1,来进行查询。
db.workmate.find(
{},
{name:1,interest:{$slice:-1},age:1,_id:0}
)
find参数:
- query:这个就是查询条件,MongoDB默认的第一个参数。
- fields:(返回内容)查询出来后显示的结果样式,可以用true和false控制是否显示。
- limit:返回的数量,后边跟数字,控制每次查询返回的结果数量。
- skip:跳过多少个显示,和limit结合可以实现分页。
- sort:排序方式,从小到大排序使用1,从大到小排序使用-1。
分页Demo:
明白了上面这些选项,现在可以作一个最简单的分页,我们把同事集合(collections)进行分页,每页显示两个,并且按照年龄从小到大的顺序排列。
dbd .workmate.find({},{name:true,age:true,_id:false}).limit(0).skip(2).sort({age:1});
$where修饰符
它是一个非常强大的修饰符,但强大的背后也意味着有风险存在。它可以让我们在条件里使用javascript的方法来进行复杂查询。我们先来看一个最简单的例子,现在要查询年龄大于30岁的人员。
db.workmate.find(
{$where:"this.age>30"},
{name:true,age:true,_id:false}
)
这里的this指向的是workmate(查询集合)本身。这样我们就可以在程序中随意调用。虽然强大和灵活,但是这种查询对于数据库的压力和安全性都会变重,所以在工作中尽量减少$where修饰符的使用。
索引:
建立索引
试着为用户名(username)建立索引。建立索引只需要一句话就可以了。
db.randomInfo.ensureIndex({username:1})
查看现有索引
db.randomInfo.getIndexes()
终端的结果,现在只有一个索引值:
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "company.randomInfo"
}
]
那现在使用命令建立一下索引db.randomInfo.ensureIndex({uername:1}),我的电脑大概要50秒左右,建立好后我们重新使用db.randomInfo.getIndexes(),查看一下结果。
结果如下:已经变成了两条索引。
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "company.randomInfo"
},
{
"v" : 2,
"key" : {
"username" : 1
},
"name" : "uername_1",
"ns" : "company.randomInfo"
}
]
然后我们查询的时间缩短到了4ms左右,查询性能提升了大概200倍左右。
总结:无论是在关系型数据库还是文档数据库,建立索引都是非常重要的。前边讲了,索引这东西是要消耗硬盘和内存资源的,所以还是要根据程序需要进行建立了。MongoDB也给我们进行了限制,只允许我们建立64个索引值。
索引中的小坑
通过实际开发和性能对比,总结了几条不用索引的情况。
- 数据不超万条时,不需要使用索引。性能的提升并不明显,而大大增加了内存和硬盘的消耗。
- 查询数据超过表数据量30%时,不要使用索引字段查询。实际证明会比不使用索引更慢,因为它大量检索了索引表和我们原表。
- 数字索引,要比字符串索引快的多,在百万级甚至千万级数据量面前,使用数字索引是个明确的选择。
- 把你经常查询的数据做成一个内嵌数据(对象型的数据),然后集体进行索引。
复合索引
复合索引就是两条以上的索引。上面已经把username字段建立了索引,我们现在把randNum0,这个字段也设置成索引。
db.randomInfo.ensureIndex({randNum0:1})
建立好后,我们再用查询索引状态命令进行查询。
db.randomInfo.getIndexes()
这时候已经是两个自建索引了,一共有三个索引。
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "company.randomInfo"
},
{
"v" : 2,
"key" : {
"username" : 1
},
"name" : "username_1",
"ns" : "company.randomInfo"
},
{
"v" : 2,
"key" : {
"randNum0" : 1
},
"name" : "randNum0_1",
"ns" : "company.randomInfo"
}
]
两个索引同时查询
我们同时查询两个索引的值,看看效果是怎么样的。
var startTime=new Date().getTime();
var db = connect('company');
var rs= db.randomInfo.find({username:'7xwb8y3',randNum0:565509});
rs.forEach(rs=>{printjson(rs)});
var runTime = new Date().getTime()-startTime;
print('[Demo]this run time is '+runTime+'ms');
从性能上看并没有什么特殊的变化,查询时间还是在4ms左右。MongoDB的复合查询是按照我们的索引顺序进行查询的。就是我们用db.randomInfo.getIndexes()查询出的数组。
指定索引查询(hint)
数字的索引要比字符串的索引快,这就需要一个方法来打破索引表的查询顺序,用我们自己指定的索引优先查询,这个方法就是hint().
var rs= db.randomInfo.find({username:'7xwb8y3',randNum0:565509}).hint({randNum0:1});
由于数据量和复杂成都还是不大,所以看不出来明显的性能提升,但是等工作中遇到大数据时,一定会得到很好的效果的。
删除索引
当索引性能不佳或起不到作用时,我们需要删除索引,删除索引的命令是dropIndex().
db.randomInfo.dropIndex('randNum0_1');//索引的唯一ID
这里需要注意的是删除时填写的值,并不是我们的字段名称(key),而是我们索引查询表中的name值;
建立全文索引
db.info.insert({contextInfo:"I am a programmer, I love life, love family. Every day after work, I write a diary."})
db.info.insert({contextInfo:"I am a programmer, I love PlayGame, love drink. Every day after work, I playGame and drink."}
db.info.ensureIndex({contextInfo:'text'})
需要注意的是这里使用text关键词来代表全文索引。
全文索引查找 建立好了全文索引就可以查找了,查找时需要两个关键修饰符:
- $text:表示要在全文索引中查东西。
- $search:后边跟查找的内容。
db.info.find({$text:{$search:"programmer"}})
** 查找多个词 **
全文索引是支持多个次查找的,比如我们希望查找数据中有programmer,family,diary,drink的数据(这是或的关系),所以两条数据都会出现。
db.info.find({$text:{$search:"programmer family diary drink"}})
如果我们这时候希望不查找出来有drink这个单词的记录,我们可以使用“-”减号来取消。
dbd .info.find({$text:{$search:"programmer family diary -drink"}})
转义符:
全文搜索中是支持转义符的,比如我们想搜索的是两个词(love PlayGame和drink),这时候需要使用\斜杠来转意。
db.info.find({$text:{$search:"\"love PlayGame\" drink"}})
用户的创建、管理、删除
创建用户:
首先要进入我们的admin库中,进入方法是直接使用use admin 就可以。进入后可以使用show collections来查看数据库中的集合。默认是只有一个集合的(system.version)。
创建用户可以用db.createUser方法来完成,里边参数还是蛮多的,代码我写在下边,然后对每一项做出了解释。
db.createUser({
user:"zhangsan",
pwd:"123456",
customData:{
name:'张三',
email:'zhangsan@126.com',
age:18,
},
roles:['read']
})
当然我们还可以单独配置一个数据库的权限,比如我们现在要配置compay数据库的权限为读写:
db.createUser({
user:"zhangsan",
pwd:"123456",
customData:{
name:'张三',
email:'zhangsan@126.com',
age:18,
},
roles:[
{
role:"readWrite",
db:"company"
},
'read'
]
})
内置角色:
- 数据库用户角色:read、readWrite;
- 数据库管理角色:dbAdmin、dbOwner、userAdmin;
- 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManage;
- 备份恢复角色:backup、restore;
- 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase
- 超级用户角色:root
- 内部角色:__system
查找用户信息
我们直接可以使用查找的方法,查找用户信息。命令很简单:
db.system.users.find()
删除用户:
删除名利也是非常简单,直接用remove就可以删除这个用户的信息和权限。
db.system.users.remove({user:"zhangsan"})
建权:
有时候我们要验证用户的用户名密码是否正确,就需要用到MongoDB提供的健全操作。也算是一种登录操作,不过MongoDB把这叫做建权。
db.auth("zhangsan","123456")
如果正确返回1,如果错误返回0。(Error:Authentication failed。)
启动建权
重启MongoDB服务器,然后设置必须使用建权登录。
mongod --auth
启动后,用户登录只能用用户名和密码进行登录,原来的mongo形式链接已经不起作。
备份和还原(不需要进入mongo):
mongodump
--host: ''
--prot: ''
--out: ''
--collection: ''
--db: ''
--username: ''
--password: ''
// 整个数据库备份
mongodump --host 127.0.0.1 --port 27017 --out D:/backup/
还原(要进入mongo, path不需要--):
mongorestore
--host: ''
--prot: ''
--username: ''
--password: ''
<path to the backup>
// 整个数据库还原(注意path不需要--;数据一致不会重新导入库中)
mongorestore --host 127.0.0.1 --port 27017 D:/backup/