MongoDB学习笔记
MongoDB笔记
一 mongodb主要组成部分:
什么是文档?文档是mongodb中的最基本数据单元,类似于关系数据库中的行。
在mongodb中,文档的键通常都是字符串。
Mongodb对文档的键是区分大小写的,对键值是区分数据类型的。
*同一文档中键值是不能重复的
什么是集合:集合是文档组成的,在关系型数据库中就是表。
在mongodb中,对集合中存储的文档是非常开放的,一个集合中可以存放各式各样的文档。
组织子集合的一种惯例是使用“.”字符分开的按命名空间划分的子集合。在MongoDB中使用子集合来组织数据是很好的方法。
例如:用户与用户详细信息包含两个集合,分别是user.user和user。userinfo。这样做的的目的只是为了使组织结构更好些,也就是说user这个集合(这里根本就不需要存在)及其子集合没有任何关系。把数据库的名字放到集合名前面,得到的就是集合的完全限定名,成为命名空间。
数据库:MongoDB中多个集合可以组成数据库。
(1)admin 从权限的角度来看,这是“root”数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或关闭服务器。
(2)local 这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合
(3)config 当mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。
命名规则:不能是“”空字符串;不可以含有空格,在mongodb中空格代表结束字符;
文档的键不可以有“.”和“$”符号,集合名中不可以包含“$”;
集合不能以“system.”开头,mongodb中system是系统保留前缀;
集合名长度不得超于121字节,在实际使用当中应该小于100字节,数据库名最多不超过64个字节(mongodb最终是以文件方式存储的,操作系统中的非法文件名,在mongodb中就是非法的,这样便于理解与记忆)
数据类型:mongodb的文件存储格式为BSON,同JSON一样,支持往其他文档对象和数组中再插入文 档对象和数组,同时扩展了JSON的数据类型与数据库打交道的那些应用。例如:JSON没有日期类型,这会使得处理本来间的日期问题变得非常繁琐,也没有办法表示其他常用类型,如正则表达式或函数。
mongodb的支持的数据类型:
null:null用于表示空值或者不存在的字段。{"x":null}
布尔:布尔类型有两个值“true”和“false”{"x":true}
浮点数:都是这种类型。例如:{"pi":3.1415926} {"pi":3}
符号:符号类型转换成了字符串
对象id:对象id是文档12字节的唯一ID {"x":Objectid()}
日期:日期类型存储的是从标准纪元开始的毫秒数。{“x”:new Date()}
正则表达式:文档中可以包含正则表达式,采用javascript的正则表达式语法:{“x”:/test/i}
代码:文档中还可以包含javascript代码。{“x”:function(){/*.....*/}}
二进制数据:二进制数据可以由任意自己的串组成。
未定义:文档中也可以使用未定义类型:{“x”:undefined}
数组:值的集合或列表可以表示数组:{“x”:["a","b","c"]}
内嵌文档:文档可以包含别的文档,也可以作为值嵌入到付文档中,数据可以组织的更自然些{"x":{"sex":"woman"}}
二、mongodb shell:是一个功能完备的javas的解释器,能够运行任意的javas。同时他是一个独立的mongodb客户端,可以执行管理操作,检查运行实例等。
运行shell:
windows:mongo.exe
linux:./mongo
shell默认连接test数据库,使用其他数据库实例,需要使用use[数据库名]命令。
使用shell窍门:由于mongo是一个javascript shell,可以查看JavaScript文档获取很多帮助,同时shell本身也内置了帮助文档,可以通过help命令查看。
数据库级帮助:db.help()
集合级帮助:db.[集合名].help()
简单查询:mongodb中查询包含find()和findOne();
find():返回值是文档集合;
findOne():返回值是一个为先匹配到的文档对象;
find和findOne函数的第一个参数决定了需要返回的文档,其形式也是一个文档,实际上可以称之为查询条件。
空的查询文档{},是匹配所有集合下的文档,不指定查询文档的情况下,mongodb默认值也是{}。比如:db.mydb.user.find()等价于db.mydb.user.find({})
findOne()也是一样。
当我们往文档里添加键/值对时,,意味着我们易筋经限定了查询条件,使用类型匹配;
例如:
查询mydb.user集合中name值为”admin“的文档集合db.mydb.user.find({name:"admin"})
查询mydb.user集合中password值为123的文档集合db.mydb.user.find({password:123})
区别于:db.mydb.user.find({password:"123"})
有时并不需要文档所有的键/值对都返回,find和findOne函数的第二个参数可以设定想要返回的键,这样做不仅可以节省传输数据量,而且可以节省客户端文档解析时间与内存消耗。
例如:db.mydb.user,find({},{name:1})
db.mydb.user.findOne({},{name:1})
创建、删除文档:就是mongodb数据的插入于删除操作。
插入并保存文档:db.mydb.user.insert({name:"jack",password:222}),在不指定”_id“值时,mongodb会自动帮我们生成”_id“键,值为Object(....),然后保存到mydb.user集合中。db.mydb.user.insert({_id:"s001",name:"jack",password:223}).mongodb则不会帮我们创建_id键。
删除制定文档:db.mydb.user.remove([需要指定条件]),例如:db.mydb.user.remove({name:"jack"})
删除整个文档集合:db.mydb.user.drop()
Mongodb插入文档(insert):插入是向MongoDB中添加数据的最基本的方法。向目标集合使用insert方法。
db.mydb.user.insert({name:"test1"})这个操作时向mydb.user集合中增加”_id“键,并添加到MongoDB中。
原理与作用:当执行插入时,使用驱动程序会将数据转换成BSON格式。数据库会解析BSON,并检验是否含有”_id“键和文档不超过16M,除之,不做任何数据验证。缺点:插入无效数据;优点:数据库远离注入式攻击。
Mongodb保存文档(save):save也是向MongoDB中添加数据的方法。
db.mydb.user.save({name:"test2"})这个操作也时向mydb.user集合中增加”_id“键,并添加到MongoDB中。
insert与save区别:save函数实际上就是根据参数条件,调用了insert或update函数。如果像插入的数据对象存在,insert函数会报错,而save函数时改变原来的对象,;如果像插入的对象不存在,那么他们执行相同的插入操作,这里可以用几个字来概括他们两个的区别,”有则改之,无则加之“。
例如:db.mydb.user.insert({name:"test1"})等价于db.mydb.user.save({name:"test1"})
db.mydb.user.insert({_id:1,name:"test3"})不等于db.mydb.user.save({_id:1,name:"test4"})
文档替换:更新文档最简单的情形就是一个新文档替换匹配的文档,适用于模式发生发生较大改变的时候。例如:{name:"test1"}修改为{name:"test1",age:20}
执行操作:db.mydb.user.update({name:"test1"},{name:"test1",age:20})
使用修改器:通常文档只需要部分更新,可使用原子的更新修改器,使得文档更新起来更高效。更新修改器是种特殊的键,用来指定复杂的操作,比如增加、删除或者调整键,还可能是操作数组或者内嵌文档。
$inc使用:“$inc”修改器是用来修改已有键的值,或者初始化某个键值(当键不存在的时候),该键值必须为数字类型,并对其进行增减操作。用法:db.集合.update({...},{$inc:{键:数字类型}})例如:db.mydb.user.update({name:"test1"},{$inc:{age:1}})
$set与 $unset:$set一般用于指定键值修改,如果指定键不存在,mongoDB会创建它,这对于文档的更新或增加键非常方便。
用法:db.集合.update({...},{$set:{键:值}})例如:为test1增加一个爱好键值db.mydb.user.update({name:"test1"},{$set:{hobby:"swimming"}})
"$set"修改器也可以内嵌文档。例如:test1的体态特征身高“175cm” db.mydb.user.update({name:"test1"},{$set:{"posture.height":"175cm"}})操作内嵌文档时,需要注意键名的书写格式:采用“:”连接方式。
“$unset”修改器用于删除键。例如test1经过人生洗礼发现自己没有爱好了
db.mydb.user.update({name:"test1"},{$unset:{hobby:175cm}})
数组修改器:是一类操作数组的特殊键,该键仅可以数组类型值。
数组修改器$push:"$push"修改器时会往键已有的数组中追加值,如果数组不存在,则创建新的数组并更新键值。
用法:db.集合.update({...},{$push:{键:值}}) 例如:db.mydb.user.update({name:"test1"},{$push:{hobby:"drink"}})
数组修改器:
$push + $ne:$push + $ne组合键使用于如果追加值不咋数组中则追加进去。
用法:db.集合.update({键:{$ne:值}},{$push:{键:值}}) 例如:db.mydb.user.update({name:"test1",bobby:{$ne:“drink”}},{$push:{hobby:"drink"}})
$addToSet:$push + $ne可以用$addToSet来实现,因为有些情况下$push + $ne组合时实现不了的。 用法:db.集合.update({...},{$addToSet:{键:值}}) 例如:db.mydb.user.update({name:"test1"},{$addToSet:{hobby:"drink"}})
$addToSet+$each:当我们一次需要追加多个值的时候,而且需要判断数组中是否存在,这时$push + $ne组合就不能实现,需要用到$addToSet+$each组合键。
用法:db.集合.update({...},{$addToSet:{键:{$each:[....]}}})
例如:db.mydb.user.update({name:"test1"},{$addToSet:{hobby:{$each:["drink","sing"]}}})
$pop:"$pop"修改器用于删除数组里面值,如果吧数组看成一个队列或者栈的话,可以从数组任何一端删除元素。
用法:{$pop:{键:1}} 从数组尾部删除一个元素
{$pop:{键:-1}} 从数组头部删除一个元素
例如:db.mydb.user.update({name:"test1"},{$pop:{hobby:1}})
$pull:"$pull"修改器也是删除数组里面的值,他可以删除数组中任意位置的值,并且删除所有匹配到的值。
用法:{$pull:{键:值}}
例如:db.mydb.user.update({name:"test1"},{$pull:{hobby:“drink”}})
数组修改器 定位修改器$:“$”定位修改器也是用于数组里面的数据类型为内嵌文档,可以使用$实行定位修改
例如:posture:[{height:"175cm",age:20},{height:"180cm",age:26}]
(1)数组都是0开头的,可以通过下标直接选择要修改的元素:db.mydb.user.update({name:"test1"},{$inc:{“posture.0.age”:1}})
(2)使用定位修改器修改:db.mydb.user.update({"posture.age":21},{"$set":{“poster.$.height”:"178cm"}})
upsert可以理解为update与insert的缩写,他是update函数的第三个参数,其实际含义就是更新插入。
update参数upsert默认值是false,他是一种特殊的更新,根据这个参数值,update函数判断对匹配不到的文档,将更新文档基于匹配文档之上以一个新的文档是否增加到集合中。
用法:db.集合.update({匹配文档},{更新文档},true)
例如:db.mydb.user.update({age:24},{$inc:{age:1}},true)
mutli:他是update函数的第四个参数,其实际含义就是是否实现更多操作。update函数的mutli默认值是false,代表只对第一个匹配到的文档进行更新操作;需要更新多个符号匹配条件的文档,则需要设置mutli值为true。
用法:db.集合.update({...},{....},false,true)
例如:所有名称为test3的年龄加2
db.mydb.user.update({name:"test3"},{$inc:{age:2}},false,true)
想要知道多少文档进行了更新,在执行完update操作之后,可以运行getLastError命令,返回值中键“n”的值就是代表更新数。
例如:db.mydb.user.update({name:"test3"},{$inc:{age:2}},false,true)
db.runCommand({getLastError:1})
返回已更新的文档findAndModify:findAndModify跟我们平时update不同,而且还有点慢,因为它需要等待数据库的回应。但对于其他原子性的查询,取值或赋值操作要方便的多。
用法:db.集合名.findAndModify({
query:{匹配文档},
update:{更新文档}
[,remove:布尔值,sort:{排序文档},new:布尔值]
})
query:匹配文档,也就是查询条件。
sort:排序匹配文档的条件
update:修改器文档,执行文档更新操作。
remove:true/false。是否对匹配到的文档进行删除。
new:true/false..表示返回的是更新前还是更新后的文档,默认值是false。
注意:update与remove必须而且只能存在一个。他一次只能操作一个文档,不能进行upsert操作,智能更新已存在的文档。findAndModify执行效率有点低,不代表避免使用,mongno每个函数的存在,都有其实际存在的价值。它的执行效率相当于一次查询、更新外加getLastError顺序执行的时间。
瞬间完成:增、删、改操作都是瞬间完成的,因为他们不需要数据库的响应,可以把他想象成客户端发出请求后就不用操心操作。我们一般称之为“离玄之箭”方式。
优点:执行效率高,它只会受客户端网络速度制约,一般情况下会工作的很好,但有时也会出意外。比如:服务器崩溃,网线被老鼠咬断,数据中心短路。。。。,对于有些应用如用户点击量或分析型数据采样,丢失几秒的数据无关紧要,但对于购物付费类的,就不好玩了
安全操作:是在执行玩操作后立即执行getLastError命令,来检查是否成功执行。然后适当的处理数据库返回的错误,一般情况下数据库会抛出一个可捕获得错误,我们可以采用自己的开发语言来捕获和处理。如果执行成功,getLastError会给出额外的信息作为响应(比如:更新或删除操作给出的更新数)
捕获“常规”错误:安全操作也是一种调试数据库“奇怪”行为的好方法,建议开发过程大量使用,这样可以避免常规的使用数据库错误。例如:“_id”值重复,mongoDB中不允许在一个集合中出现重复“_id”值的文档。
{_id:123,name:"test1"}和{_id:123,name:"test2"}
请求与链接:数据库会为每一个mongoDB链接创建一个独立的队列,来存放连接的请求,当客户端发送请求,会被存放在该连接队列的末尾,当队列的请求都执行完毕以后,才会执行后续的请求。对于实际应用的交错插入,查询,会产生秒级的延迟。
查询 find:查询就是返回一个集合中文档的子集,子集合的范围从0个文档到整个集合。
例如:db.mydb.user.find({name:"test1"})
指定返回键:有时并不需要返回文档中的所有键和值,遇到这样的情况,使用find的第二个参数来制定返回键,这样做既可以节省传输的数据量,也可以节省客户端解码文档的时间和内存开销。
例如:db.mydb.user.find({},{age:1})
限制:查询还是有些限制的,数据库所关心的查询文档的值必须是常量,也就是不可以使用文档的其他键值。
查询条件:< :$lt(小于) 例如:db.mydb.user.find({age:{$lt:35}})
<= :$lte(小于等于) 例如:db.mydb.user.find({age:{$lt3:35}})
> :$gt(大于) 例如:db.mydb.user.find({age:{$gt:31}})
>= :$gte(大于等于) 例如:db.mydb.user.find({age:{$gte:31}})
≠ :$ne(不等于) 例如:db.mydb.user.find({age:{$ne:"test3"}})
模糊查询:/^.../ 例如:db.mydb.user.find({name:{$lt:/^test/})
or查询:or查询在mongodb中有两种方式:$in($nin)与$or
$in:是用来查询一个键的多个值。
例如:(1)db.mydb.user.find({name:{$in:["test1","test2"]}})
(2)db.mydb.user.find({_id:{$in:["123",1]}})
与之相对的则是$nin:用来查询条件不匹配的所有文档。
$or:比$in更通用一些,是用来查询多个键的任意给定键值,其值是一个数组形式。
例如:db.mydb.user.find({$or:[{name:"test1"},{age:25}]})
$or也可以与$in或$nin结合起来使用 db.mydb.user.find({$or:[{name:{$in:["test1","test2"]}},{age:25}]})
$not:是用来查询与特定模式相符的文档。
例如:db.mydb.user.find({age:{$not:{$mod:[5,0]}}})
$not与正则表达式是联合使用的时候极为有用。
条件句规则:在查询中,类似$lt的键外在内层文档,如修改器$inc则处在外层文档。如:{age:{$lt:30}}与{$inc:{age:1}}而且一个键可以含有多个条件,但不可以含有多个修改器:正确:{age:{$lt:30,$gt:20}} 错误 {$inc:{age:1},$set:{age:40}}
键值为数组的查询:在实际应用中,对于一个键值为数组的查询时非常常见的,如下:db.mydb.insert({shop:"A",fruit:["apple","banana","orange"],size:3}) 采用查询:db.mydb.find({fruit:"apple"})
$all :对于数组查询中,多个数组的匹配时,需要用到关键键$all。如下:
db.mydb.insert({shop:"B",fruit:["apple","peach","orange"],size:3})
db.mydb.insert({shop:"C",fruit:["mango","banana","orange"],size:3})
查询既有“apple”又有“banana”的文档:db.mydb.find({fruit:{$all:["apple","banana"]}})
注意:$all查询不考虑键值的先后顺序问题。
精确匹配:使用[]精确匹配数组元素,既要考虑键值的个数,也要考虑元素的位置。
db.mydb.find({fruit:["apple","banana","orange"]})
下标查询:Key.index key:代表键 index:数组下标
数组键值下标是从0开始。如下:db.mydb.find({“fruit.1”:"banana"})查询数组中第二的元素为“banana”的文档。
$size:在数组查询中,该键就是查询指定长度的数组。如:db.mydb.find({fruit:{$size:3}}),但他不可以与其他查询子句组合使用(“$lt”,"$gt");如:db.mydb.find({fruit:{$size:{$gt:3}}})是非法的。但是这种查询可以通过在文档中增加一个size键来实现的。如:db.mydb.update({shop:"A"},{"$push":{fruit:"watermelon"},$inc:{size:1}})
db.mydb.find({size:{"$lt":4}})
$slice是用于自己查询,其返回值是一个子集合,如:db.mydb.find({shop:"A"},{fruit:{$slice:2}}),查询表示查询数组的前2个子集。
也可以返回后2个子集:db.mydb.find({shop:"A"},{fruit:{$slice:-2}}),它也可以接受偏移量[m,n]:db.mydb.find({shop:"A"},{fruit:{$slice:[1,2]}})表示跳过第一个查询第2个子集
键值为文档的查询:对于内嵌文档的查询有两种查询方式,一种是全文档匹配,另一种是键/值匹配,如:{study:"java",person:{name:"zhangsan",age:25}}
采用全文档匹配查询:db.mydb.km.find({person:{name:"zhangsan",age:25}})
另一种查询方式:使用.连接符的键值匹配。如下:db.mydb.mk.find({"person.name":"zhangsan","person.age":25})这种点的查询方式就是区别于其他普通个文档的主要特点,而且为什么对于键名不能使用.这个符号做出最好的诠释。
注意:键值匹配查询,对于内嵌文档值多个或者顺序未知的情况下,是有效的。
$elemMatch:该键是用来匹配数组中单个内嵌文档使用的。db.mydb.km.find({person:{$elemMatch:{name:"zhangsan",age:27}}})
$where查询:当键、值查询不能满足需求的时候,我们需要用到$where查询子句。是借助javascript来完成的
查询apple和芭娜娜值相等的文件:$where表达式来执行:
db.mydb.fruit.find({"$where":function(){
if(this.apple==this.banana) return true;
return false;
}})等价于db.mydb.fruit.find({"$where":"this.apple==this.banana"})
注意:$where查询其返回值是表达式返回true的当前文档
$where的优缺点:
优点:由于它可以使用JavaScript表达式,查询灵活多样。
缺点:使用$where查询,查询速度慢,因为其查询原理是将被查询文档转化为JavaScript对象,然后通过$where表达式去比较,而且还不能使用索引
limit:是限制返回结果集的数量,在find函数后使用该函数。
limit(n):返回匹配n个文档。这里的n是上限,不是下限。
例如:db.mydb.fruit.find().limit(3)//返回匹配到的3个文档,如果结果少于3个,则全部返回。
skip(n):略过匹配到的前n个文档,返回剩下的文档。
例如:db.mydb.fruit.find().skip(3)//略过匹配到的前3个文档,返回余下的所有文档。如果结果少于3个,则返回空。
sort:是一个排序函数,他的参数一个文档对象(键、值),键是集合中的键,值是1或-1;1代表升序,-1代表降序。
例如:db.mydb.fruit.find().sort({apple:1,banana:-1})由于文档型数据库的数据类型是不规则的,但mongo预定义了数据类型的排序规则。如:{a:[1,2]}、{a:true}
排序规则:从小到大
1.最小值 2.null 3.数字 4.字符串 5.文档 6.数组 7.二进制 8.对象ID 9.布尔型 10.日期 11.时间戳 12.正则表达式 13.最大值
随机抽取文档:在实际应用中,随机抽取一个或多个文档是常见的需求,最简单的实现方法:先计算出一个从0到记录总数之间的随机数,然后采用skip(随机数)方法
var total=db.mydb.fruit.count()
var random=Math.floor(Math.random()*total);
db.mydb.fruit.find().skip(random).limit(1)
当数据量很大时,skip操作会变得很慢,可以采用另外一种查询方式:为每一条记录增设random字段,并赋值为Math.random(),查询时采用$gte和$lte.
db.mydb.fruit.update({banana:1},{$set:{random:Math.random}})
var rand=Math.random();
db.mydb.fruit.find({random:{$gte:rand}});
借助mongodb对地理空间索引的支持,从而可以在上一个方法的基础上来实现随机记录的获取。
创建地理空间索引:
db.mydb.fruit.ensureindex({random:"2d"})
db.mydb.fruit.update({banana:1},{random:[Math.random(),0]})
db.mydb.fruit.find({random:{$near:[Math.random(),0]}})
null:在查询中,null有点奇怪,它不仅可以匹配键值为null的文档,而且还可以匹配“不存在”的键。如果仅仅想要匹配键值为null的文档,既要判断该键值是否为null,还要通过“$exists”判断该键是否存在。
例如:db.mydb.test.find({z:{$in:[null],$exists:true}})
正值表达式:正则表达式能更灵活有效的匹配字符串。mongoDB使用perl兼容的正值表达式(PCRE)库来匹配正值表达式,PCRE所支持的正值表达式都能被MongoDB接受,正值表达式也可以匹配自身。
例如:{name:/jack/} {name:/jack/i} {name:/jac?/i} {name:/^ja/}
聚合查询:
count:它的查询方式跟find一样,但返回值不一样。find返回值是文档集,count返回值是文档数。
例如:db.mydb.fruit.count() 查询文档总数
db.mydb.fruit.count({banana:1}) 查询限定文档总数
distinct是键值去重,其返回值是一个数组。
用法:db.mydb.fruit.distinct(“键”[,查询条件])
如:db.mydb.fruit.distinct("apple")
db.mydb.fruit.distinct("apple".{banana:1})
group:在mongodb里面做group操作有点小复杂,利用group可以将集合中的记录按照一个或多个键分组,然后可以聚合每个分组内的文档产生新的结果。
用法:db.mydb.fruit.group({"key":{},"$reduce":function(doc,prev){},"initial":{}})
key分组的键
initial:累加器初始值,分组之后,每一组的reduce函数会调用这个初始文档,并在组内传递累加器;
$reduce:分组处理函数,分组之后会对每组元素遍历处理,该函数两个参数,第一个参数是当前的文档对象和第二个参数是上一次function操作的累计对象。
group返回值:结果返回值为文档数组:[{分组建,累加器}]
例如:db.mydb.fruit.db.group({"key":{apple:1},"$reduce":function(doc,prev){pre.bananaArr.push(doc.banana)},"initial":{bananaArr:[]}})