深入浅出MongoDB应用实战开发

时间:2022-04-11 07:24:04

写在前面的话:

这篇文章会有点长,谨此记录自己昨天一整天看完《深入浅出MongoDB应用实战开发》视频时的笔记。只是在开始,得先抛出一个困扰自己很长时间的问题:“带双引号的和不带双引号的json有啥区别?"也许,就有人知道呢?

1.多个链接:

2.关于mongodb的安装,在此就不赘述,有兴趣的话可以参考这里

3.开始入手之前,其实可以这样类比:

深入浅出MongoDB应用实战开发

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  一般都是在客户端产生的

深入浅出MongoDB应用实战开发

***概念:是一个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进行分页的方法(参考源码分析)

深入浅出MongoDB应用实战开发

游标内幕:

  • 服务器端,游标消耗内存和其他资源,要尽快合理释放
  • 游标遍历完成或客户端发消息终止,释放游标
  • 游标在客户端不在作用域,驱动会向服务器发消息销毁游标
  • 超时销毁机制,游标即使是在客户端作用域内,但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}


 写在后面的话:

真心觉得这篇文章太长,不过想要多了解一点知识,好像就得先经过这么个阶段,希望自己继续。