深入浅出MongoDB应用实战开发
写在前面的话:
这篇文章会有点长,谨此记录自己昨天一整天看完《深入浅出MongoDB应用实战开发》视频时的笔记。只是在开始,得先抛出一个困扰自己很长时间的问题:“带双引号的和不带双引号的json有啥区别?"也许,就有人知道呢?
1.多个链接:
- 视频地址:http://study.163.com/course/courseMain.htm?courseId=1004226004;
- bson文档:http://bsonspec.org/;
- json文档:http://www.json.org/
2.关于mongodb的安装,在此就不赘述,有兴趣的话可以参考这里;
3.开始入手之前,其实可以这样类比:
4.文档:
***概念:多个键及其关联的值有序的放置再一起就是文档;
***单键值文档:{"userName":"bbs12"}
***多键值文档:{"_id":"456121d454d444e4154dgvb","userName":"zhangsan","pwd":"123445"}
***命名规则:
- 文档中键/值对是有序的 (键的顺序改变就为新的文档)
- 比如:{"_id":"456121d454d444e4154dgvb","userName":"zhangsan","pwd":"123445"}与{"_id":"456121d454d444e4154dgvb","pwd":"123445","userName":"zhangsan"}就是两个文档
- 文档中的值不仅可以是字符串,也可以是其他数据类型(或者潜入其他文档)
- 键是字符串,键可以使用任意的UTF-8字符
- 键不能含有\0(空字符),空字符表示键的结尾
- .和$做为保留字符,通常不应出现在键中
- 以下划线“_”开头的键通常情况下是保留的
- mongdb不但区分数据类型,也区分大小写(这点与sql有点不一样)
- 比如:{"userName":"zhangsan"}与{"userName":zhangsan}不同 {"user":"lisi"}与{"User":"lisi"}不同
- 文档中不允许有重复的键
- 比如:{"user":"bbs"}与{"user":"bbs11"}非法
5.集合:
***概念:集合就是一组文档,类似于关系数据库的表
***集合是无模式的,mongdb对模式不做强制要求,由开发者灵活把握
***命名规则:
- 集合名不能为空字符串""
- 不能含有空字符串\0
- 不能以"system."开头,这是系统集合保留的前缀
- 集合名不能含有保留字符$
- 组织集合的一种惯例是以.分开,近命名空间划分子集合,例如system.users,system.indexes
- (尽量将同类型的文档放在同一个集合中,对于数据库的性能等都有好处)
6.数据库:
***概念:多个集合组成数据库
***一个mongdb实例可承载多个数据库,互相之间彼此独立
***开发中通常将一个应用的所有数据存放到同一个数据库中
***磁盘上,mongdb将不同数据库存放在不同文件中
***命名规则:
- 数据库名为UTF-8字符串,最长64个字符
- 不能是空字符串 ""
- 不能含"、.、$、 /、 \和\0
- 应全为小写
7.系统保留数据库:
***admin:这是root数据库,添加用户到该数据库中,该用户会自动继承所有数据库权限
***local:这个数据库中的数据永远不会被复制,可以用存储限于本地数据单台服务器的任意集合;
***config:分片时,config数据库在内部使用,保存分片信息
***把数据库名放集合名前,得到的就是集合的完全限定名称,叫命令空间。命令空间长度不能超过121字节,实际使用时应小于100字节
8.mongodb的数据类型:
*** MogonDB使用BSON数据格式作为数据存放结构,BSON数据格式就是JSON数据格式的二进制文件;
***为什么在mongodb中使用bson而不是json呢?
Ⅰ比json更省空间;Ⅱ支持的数据类型更为广泛 :是json的一种二进制格式;
***实际上,mongdb的数据类型就是bson所支持的数据类型;
不一一罗列,可以在bson官网中找到:
- null 表示空值或者未定义的对象 {"x":null}
- 布尔值 真或者假 true或者false {"x":true}
- 32位整数 32位整数 shell是不支持该类型的,默认会转换成64位浮点数
- 64位整数 64位整数 shell是不支持该类型的,默认会转成64位浮点数
- 64位浮点数 64位浮点数 shell中的数字就是这一种类型 {"x":3.14,"y":3}
- 字符串 UTF-8字符串 {"foo":"bar"}
- 符号 shell不支持,会将数据库中的富豪类型的数据自动转换成字符串
- 对象id 文档的12字节的唯一id {"id":ObjectId()}
- 日期 从标准纪元开始的毫秒数 {"data":new Date()}
- 正则表达式 文档中可以包含正则表达式,遵循js的语法 {"foo":/foorbar/i}
- 代码 文档中可以包含js代码 {"x":function(){}}
- 二进制数据 任意字节的二进制串组成,shell不支持
- 最大值 BSON 包括一个特殊类型,表示可能的最大值,shell不支持
- 最小值 BSON 包括一个特殊类型,表示可能的最小值,shell不支持
- 未定义 undefined {"x":undefined}
- 数组 值的集合或者列表 {"arr":["a","b"]}
- 内嵌文档 文档可以作为文档中某个key的value {"x":{"foor":"bar"}}
9.ObjectId 一般都是在客户端产生的
***概念:是一个12字节 BSON 类型数据,有以下格式:
- 前面四个字节代表从标准纪元开始的时间戳,以秒为单位
- 接下来三个字节表示机器号,一般是机器名的hash值,这可以保证不同机器产生的id不会冲突
- 接下来的两个字节表示进程id号(PID),保证统一机器不同建成产生的id不冲突
- 最后三个是计数器的计数值,对于任意一秒钟,可以产生2^24个数,是个随机数
MongoDB Shell是MongoDB自带的交互式Javascript shell,用来对MongoDB进行操作和管理的交互式环境;如果你需要进入MongoDB后台管理,你需要先打开mongodb装目录的下的bin目录,然后执行mongo.exe文件;
shell相当于mongdb的客户端;
shell常用命令:
- show dbs 查看已有数据库列表
- show collections 查看已有集合列表
- show users 查看已有用户列表
- user dbname 切换数据库,系统会自动延迟创建该数据库
- db.account.save({"name":"test","addr":"China"}) 创建集合
- db.account.find() 查看集合数据
- db.dropDatabase() 删除数据库
10.文档插入:
***单个文档插入:db.account.insert({"userName":"zhangsan","pwd":"123456","acctAttr":null})
***批量插入:受mongdb消息大影响,最大消息16M
***插入原理:
驱动将文档转为bson,检查是否有id键,传入数据库,数据库解析bson,不做有效性校验,原样存入数据库中
***文档最大消息不能超过4M
11.文档删除:
***删除文档中所有数据:db.account.remove() 不删除索引
***条件删除: db.account.remove({"userName":"zhangsan"})
***删除整个集合:db.account.drop() 数据、索引一起删除,性能好
12.文档更新
***更新命令:(语法:需要查找的文档 ---> 更改为后面这个值 )
db.account.update({"userName":"zhangsan1"},{"_id":"3e1fd26f4b0f8351760fcc54"},"userName":"lisi","acctAttr":null})
加1:db.account.update({"userName":"bbs10"},{"$inc":{"age":1}})
减1:db.account.update({"userName":"bbs10"},{"$inc":{"age":-1}})
***set用法:使用修改器进行局部更新
db.account.update({"_id":"3e1fd26f4b0f8351760fcc54"},{"$set":{"pwd":"123456"}})
***去掉一个键:(删除{"userName":"zhangsan"}这个文档中的pwd字段)
db.account.update({"userName":"zhangsan"},{"$unset":{"pwd":1}})
***$inc 用法 :($inc的键值必须为数值)
db.account.update({"userName":"bbs10"},{"$inc":{"age":30}})
***数组修改器$push(相当于压栈 push加进去的 值为数组)
db.account.update({"userName":"bbs10"},{"$push":{"email":"1231@163.com"}})
***$addToSet避免重复加入
db.account.update({"userName":"bbs10"},{"$addToSet":{"email":"1232@163.com"}})
***pop修改器(和push对应)
db.account.update({"userName":"bbs10"},{"$pop":{"email":1}}) //1:从数组尾删除一个元素
***从数组头删除一个元素
:db.account.update({"userName":"bbs10"},{"$pop":{"email":-1}})
***指定位置删除元素
db.account.update({"userName":"bbs10"},{"$pull":{"email":"1232@163.com"}})
***多文档更新:(这里的解释。。。。)
db.account.update({"userName":"bbs10"},{"$set":{"pwd":"1111"}},false,true)
13.文档查询
***基本在mysql中可以做到的在mongdb中也可以做到
基础查询find命令:
***查询集合所有文档:db.account.find()
***简单条件查询:db.account.find({"userName":"bbs10"})
***多值匹配条件查询,类似"条件1 and 条件2" select * from account where userName="bbs10" and "pwd"="1111":db.account.find({"userName":"bbs10","pwd":"1111"})
***指定返回某些键:(1:表示返回 0:表示不返回)
db.account.find({},{"userName":1,"pwd":1})
db.account.find({},{"pwd":0})
db.account.find({},{"userName":1,"_id":0})
***复合条件查询:
***比较操作符 lt 、lte、 gt、 gte、 ne 分别对应 <、 <=、 >、 >=、 !=
db.account.insert({"userName":"zhangsan","pwd":"123456","creatTime":new Date()})
db.account.find({"creatTime":{"$gt":start}})
db.account.find({"age":{"$gt":30,"$lt":40}})
db.account.find({"age":{"$gte":30})}
$in查询 :db.account.find({"age":{"$in":[30,32]})
*********其实是对照sql来做的
***db.account.find({"age":{"$nin":[30,32]}) //notin
$or 或查询,可多键值或查询:db.account.find({"$or":[{"userName":"zhangsan"},{"age":32}]})
***组合查询 :db.account.find({"$or":[{"userName":"zhangsan"},{"age":[30,32]}]})
***$not运算符,可用于任何条件之上,表示取非:
db.account.find({"age":{"$not":{"$nin":[30,32]}}})//年龄在30,32的
***$mod模运算:db.account.find({"age":{"$mod":[5,0]}})//用5去除,余数为0的文档
高级查询规则 ——null
***null 不仅匹配自身,还匹配不存在
db.account.find({"creatTime":null})
***要结合$exist才能准确查出属性为null的文档:
db.account.find({"creatTime":{"$in":[null],"$exists":true})//这个属性存在并且=null
高级查询规则 ——正则表达式
***正则表达式遵循js正则表达式规则:
db.account.find({"userName":/zhangsan/i})
***带前缀正则表达式查询性能最好:
db.account.find({"userName":/^zhangsan/i})
高级查询规则 ——数组
db.account.insert({"_id":1,"fruit":["apple","banana","peach"]})
db.account.insert({"_id":2,"fruit":["apple","watermelon","orange"]})
db.account.insert({"_id":3,"fruit":["cherry","banana","apple"]})
***单元素匹配任意一个就行:db.account.find({"fruit":"apple"})
***多元素匹配要用$all,既有apple又有banana的文档;//and db.account.find({"fruit":{$all:["apple","banana"]}})
***数组下标从0开始,用数组下标指定位置查询:db.account.find({"fruit.2":"apple"})
***$size,查询指定数组长度的数组:db.account.find({"fruit":{"$size":3}})
高级查询规则 ——查询内嵌文档
***db.account.insert({"userName":{"first":"joe","last":"schome"},"age":30})
***完全匹配查询:db.account.insert({"userName":{"first":"joe","last":"schome"}})
***改变文档数据模式:db.account.update({},{"$set",{"userName.pwd":"1111"})
***点表示法查询(不受文档数据模式改变影响):db.account.find({"userName.first":"joe","userName.last":"schome"})
***再次改变文档数据模式,增加数组元素
db.account.update({},{"$push":{"comments":{"author":"joe","score":3,"comment":"test"}}})
db.account.update({},{"$push":{"comments":{"author":"tom","score":5,"comment":"test"}}})
***查询作者为joe,并且得分超过5分的文档:db.account.find({"comments.author":"joe","comments.score":5})
***正确做法,采用$elemMatch对内嵌文档多键匹配:db.account.find({"comments":{"$elemMatch":{"author":"joe","score":{"$gte":5}}})
where查询
db.food.insert({"apple":1,"banana":6,"peach":3})
db.food.insert({"apple":8,"spinach":4,"watermelon":4})
1 db.food.find({"$where":function(){ 2 for(var current in this){ 3 for(var other in this){ 4 if(current !=other && this[current] ==this[othor]){ 5 return true; 6 } 7 } 8 } 9 return false; 10 } 11 });
***不是逼急了不要使用where查询,能用键/值对尽量用键/值对查询方式;
***where查询无法使用索引,并且文档要从bson转成js对象,查询速度非常慢
游标操作
***for(i=0;i<100;i++){db.c.insert({x:1});}
***定义游标,不会立即执行查询:
***var cursor = db.c.find()
***游标迭代器:
***while(cursor.hasNext()){obj=cursor.next()}
***执行cursor.hasNext()时,查询发往服务器。执行真正查询,shell会获取前100个结果或者4M
***数据(两者较小者)返回
***限制结果数据:db.c.find().limit(5);
***跳过匹配文档:db.c.find().skip(5);
***排序:sort用一个对象为参数,键、值对表示,键对应文档键名,值表示排序方向,
***1:升序; -1:降序
***db.c.find().sort({x:-1}) db.c.find().sort({x:1})
***多键复合排序:db.account.find().sort({userName:1,age:-1})
游标分页
***for(i=0;i<100;i++){db.c.insert({x:i,y:"post"});}
***按条件查询每页20条记录
***db.c.find({"y":"post"}).limit(20).sort({"x":1})
***db.c.find({"y":"post"}).limit(20).sort({"x":-1})
***分页参数就是skip的参数 db.c.find({"y":"post"}).limit(20).skip(20).sort({"x":1})
***注意:用skip跳过少量文档时可行的,数量太多就会变慢
***另一种不用skip进行分页的方法(参考源码分析)
游标内幕:
- 服务器端,游标消耗内存和其他资源,要尽快合理释放
- 游标遍历完成或客户端发消息终止,释放游标
- 游标在客户端不在作用域,驱动会向服务器发消息销毁游标
- 超时销毁机制,游标即使是在客户端作用域内,但10分钟不用,也会自动被销毁
- 若关闭游标时销毁机制(驱动immortal函数),游标使用完,一定要显式将其关闭,
- 否则其会一直消耗服务器资源。
索引创建
1 for(i=0;i<10000;i++){ 2 db.account.insert({ 3 userName:"bbs"+i, 4 age:i%60, 5 creatTime:new Date()}) 6 }
*单键索引:
db.account.ensureIndex({"userName":1})
*复合索引:
***db.account.ensureIndex({"userName":1,"age":-1})
***db.account.ensureIndex({"userName":1,"age":1,"creatTime":1})
***只有索引前部的查询会得到优化
***索引优化查询的同时,会对增删改带来额外开销
索引创建原则
- 应用程序会做什么查询,哪些键需要索引
- 每个键的索引方向时怎样的
- 如何应对扩展?如何通过不同键的排列使数据尽可能多的保存在内存中,增加命中率
- 数据量大时为排序创建索引
唯一索引
***db.account.ensureIndex({"userName":1},{"unique":true})
***默认插入数据时不检验数据唯一性
***安全插入时才会检查数据唯一性
***_id键索引就是唯一索引,并且不能删除
***也可以创建唯一复合索引,单键可重复,组合后不相同即可
查询分析工具
***explain可分析查询使用索引的情况,耗时,及扫描文档数的统计
***db.account.find({"userName":"bbs22"}).explain()
***db.account.find({"age":30}).sort({"userName":1}).explain()
***db.account.find({"age":{$gt:20,$lt:30}).explain()索引前后比较
强制指定索引使用:
***hint可以强制mongddb使用某一个索引
***db.account.find({"age":{$gt:20,$lt:30}).hint({"userName":1,"age":1,"creatTime":1}).explain()
***通常mongodb会智能选择最优索引使用
索引管理
***索引存储在每个数据库的system.indexes集合中,这个是一个保留集合,不能直接对其数据进行增删改,只能通过ensureIndex和dropIndex来操作
***db.system.indexes.find()
***db.system.namespaces.find()
***修改索引 db.account.ensureIndex({"age":-1},{"background":true}) background参数表示创建索引时在后台创建,不阻塞正常请求
***删除索引:db.runCommand({"dropIndexes":"account","index":"age_-1"})
count
***统计集合总数:db.account.count()
***条件统计:db.account.count({"age":30})
***增加查询条件统计通常用于分页查询,但增加查寻条件统计会使统计变慢
distinct
***用于找出指定键的不同值
db.runCommand({"distinct":"account","key":"age"})
相当于:select distinct age from account
***必须指定集合名、键名
***计算distinct后的count总数
db.runCommand({"distinct":"account","key":"age"}).values.length
相当于:select count(distinct age) from account
db.runCommand({"distinct":"account","key":"age","query":{"age":{"$gt":30}}}).values.length
相当于:select distinct age from account where age>30
group
***db.account.group({"key":{"age":true},initial:{number:0},
reduce:function(doc,prev){prev.number+=1},
cond:{"age":{"$gt":30}}
})
类似于:select count(*) from account group by age where age>30
***key关键字标识我们想要汇总的数据(类似于sql中group by子句的参数)
***initial 关键字标识我们记录的关键字的关键值的初始值
***reduce关键字定义了一个方法,每次遇到一个文档要做的事情
***cond关键字定义条件
命令的工作原理
db.account.drop()
db.runCommand({"drop":"account"})
***命令响应结果时一个文档,ok键值为true表示执行成功,为false表示执行失败
***errmsg键值表示失败原因
***mongdb中的命令其实时作为一种特殊类型的查询来执行的,这些查询针对$cmd集合来执行,
runCommand仅仅命令文档,执行等价查询
db.$cmd.findOne({"drop":"c"})
命令参考
***支持的命令浏览:
db.listCommands()
***访问web console也可获取
***版本命令:db.runCommand({"buildInfo":1})
***集合统计信息:db.runC ommand({"collStats":"account"})
***修复数据库:{"repaireDatabase":1}比较耗时
***删除集合:{"drop":collection} ——> db.runCommand({"drop":collection})
***查看对本集合最后一次操作的错误信息:
db.runCommand({"getlasterror":1,"w":1,"wtimeout":0})
***删除当前数据库:{"dropDatabase":1}
***删除索引:{"dropIndexes":"collection","index":name}
***检查本服务器是主服务器还是从服务器:{"isMaster":1} ——> db.runCommand({"isMaster":1})
***列出服务器上所有数据库(管理员身份专用):{"listDatabases":1}
***重命令集合:{"renameCollection":a,"to":b} //把集合a的名字改为集合b
db.runCommand({"renameCollection":"bbs.account","to":"bbs.b"})
a、b均必须为完整的命令空间(管理员身份专用)
***服务器管理统计信息:{"serverStatus":1}
写在后面的话:
真心觉得这篇文章太长,不过想要多了解一点知识,好像就得先经过这么个阶段,希望自己继续。
作者:郑叶叶
出处:http://www.cnblogs.com/zhengyeye
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。