• 深入浅出MongoDB应用实战开发


    写在前面的话:

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

    1.多个链接:

    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字符
    • 键不能含有(空字符),空字符表示键的结尾
    •   .和$做为保留字符,通常不应出现在键中
    • 以下划线“_”开头的键通常情况下是保留的
    • mongdb不但区分数据类型,也区分大小写(这点与sql有点不一样)
    • 比如:{"userName":"zhangsan"}与{"userName":zhangsan}不同  {"user":"lisi"}与{"User":"lisi"}不同
    • 文档中不允许有重复的键
    • 比如:{"user":"bbs"}与{"user":"bbs11"}非法

    5.集合:

    ***概念:集合就是一组文档,类似于关系数据库的表

    ***集合是无模式的,mongdb对模式不做强制要求,由开发者灵活把握

    ***命名规则:

    • 集合名不能为空字符串""
    • 不能含有空字符串
    • 不能以"system."开头,这是系统集合保留的前缀
    • 集合名不能含有保留字符$
    • 组织集合的一种惯例是以.分开,近命名空间划分子集合,例如system.users,system.indexes
    • (尽量将同类型的文档放在同一个集合中,对于数据库的性能等都有好处)

    6.数据库:

    ***概念:多个集合组成数据库

    ***一个mongdb实例可承载多个数据库,互相之间彼此独立

    ***开发中通常将一个应用的所有数据存放到同一个数据库中

    ***磁盘上,mongdb将不同数据库存放在不同文件中

    ***命名规则:

    • 数据库名为UTF-8字符串,最长64个字符
    • 不能是空字符串 ""
    • 不能含"、.、$、 /、  和
    • 应全为小写

    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}


     写在后面的话:

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

  • 相关阅读:
    【Tools__技巧】软件技巧整理归纳
    【JS__框架】主页面F5刷新iframe框架页面
    为类型增加选择属性
    单元测试mock当前时间
    silverlight属性改变事件通知
    修改博客园推荐人数增加W效果
    Enum扩展特性,代替中文属性
    Python爬虫-萌妹子图片
    吐槽下魅族
    使用openXML 不用插件导出excel
  • 原文地址:https://www.cnblogs.com/zhengyeye/p/9096133.html
Copyright © 2020-2023  润新知