Mongodb浅解

来源:互联网 发布:关于网络的论文 编辑:程序博客网 时间:2024/06/05 16:25

1.数据库概念:
数据库:数据存储的仓库
数据库就是为我们方便的管理数据的一个平台,例如对数据的存储、修改、查询等都非常的方便。

2.数据库分类
数据库产品有很多,以下是一些常见的数据库产品:
•MySQL
•Oracle
•DB2
•SqlServer
•MongoDB

数据库没有排名之分,各有各的应用场景,我们这里学习的是 MongoDB 数据库。
MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。
MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。

3.MongoDB 数据库
为了更好的学习 MongoDB 数据库的基本使用,大家可以参考以下教程上的 MongoDB 数据库教程文档,
http://www.yiibai.com/mongodb/mongodb_create_collection.html
http://www.runoob.com/mongodb/mongodb-tutorial.html
mongodb数据库官网:https://www.mongodb.com/

4.为什么选择MongoDB?
理由:
1.只要会js就会mongodb(学习成本低),不用写SQL语句,而是用BSON的方式操作数据库,简单易学,容易上手,开发效率高!
2.mongodb对js的兼容性最好
3.mongodb存数据就是存JSON
4.MongoDB的性能高!
5.传统数据库要考虑是不是要预留字段,以及后期有可能还要修改!
6.完美支持各种语言
7.支持BSON数据格式(对json类型的拓展,将JSON转换成二进制码,存储到数据库中,支持更多的数据类型,如:Date数据类型,更快的遍历速度,对JSON格式来说,太大的JSON结构会导致数据遍历非常慢!)

######################################### MongoDB数据库/服务器的安装

5.MongoDB在Linux系统上的安装
1.下载安装包 wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel62-3.4.2.tgz
2.下载完成后解压缩压缩包 tar zxf mongodb-linux-x86_64-rhel62-3.2.0.tgz -C ../installDir/
3.切换到MongoDb的解压目录 :cd ../installDir/
4.修改文件夹的名称:mv mongodb-linux-x86_64-rhel62-3.2.0 mongodb3-2
5.进入到 mongodb3-2目录下: cd mongodb3-2
6.创建数据库文件夹与日志文件: mkdir data;touch logs
7.配置环境变量/etc/profile
export MONGODB_HOME=/opt/installDir/mongodb3-2
export PATH=PATH:MONGODB_HOME/bin

8.启动mongodb        A:下面这个是需要权限的登录方式, 用户连接需要用户名和密码            mongod --dbpath=/opt/installDir/mongodb3-2/data --logpath=/opt/installDir/mongodb3-2/logs --logappend  --auth  --port=27017 --fork        B:这个是不需要密码的            mongod --dbpath=/opt/installDir/mongodb3-2/data --logpath=/opt/installDir/mongodb3-2/logs --logappend  --port=27017 --fork    注意:MongoDB数据库在默认是没有用户名及密码,不用安全验证的,只要连接上服务就可以进行CRUD操作     参数解释: (可通过mongod --help来查看)        --dbpath 指定数据库的目录        --logpath 指定日志文件路径        --logappend 日志文件末尾添加        --port 指定服务器监听的端口号,默认是27017        --fork 在后台以守护进程的方式运行MongoDB        --auth 是否需要验证权限登录(用户名和密码)        --config 指定启动项所用文件的路径(-f 的功能类似于这个--config)        --master 指定为主机器        --slave 指定为从机器        --source 指定主机器的IP地址        --pologSize 指定日志文件大小不超过64M.因为resync是非常操作量大且耗时,最好通过设置一个足够大的oplogSize来避免resync(默认的 oplog大小是空闲磁盘大小的5%)。        --only 指定只复制哪一个数据库        --slavedelay 指从复制检测的时间间隔           netstat -anlp|grep 27017        ps -ef | grep mongo9.进入数据库的CLI管理界面    执行命令 mongo10.若数据库出现如不能连上,则是一个data目录下的mongod.lock文件的问题,可以用如下的修复的命令,    mongod --repair12、设置开机自启动    1)在mongodb安装目录下创建并编辑脚本mongo.conf,内容参考我们发送的mongo.conf文件    2)在/etc/init.d/目录下新建立一个mongodb脚本,内容参考我们发送的mongodb文件    3)要给它赋予执行权限:        chmod +x /etc/init.d/mongodb    4)接着试一下是否可以启动、停止:        service mongodb start        service mongodb stop    3)最后设为开机启动:        chkconfig --add mongodb         chmod +x  mongodb         chkconfig mongodb on设置用户名和密码请参考:http://blog.sina.com.cn/s/blog_6e4059a40101b5z7.html后台运行:    如果使用--fork在后台运行mongdb服务,那么就要通过向服务器发送shutdownServer()消息来关闭。    1、普通命令:    $ ./mongod    > use admin    > db.shutdownServer()    要注意的是,这个命令只允许在本地,或是一个经过认证的客户端。

====================================================MongoDB客户端连接测试================================================

1.在windows系统上mongodb客户端的安装(提示:一直next就好)

2.关闭Linux系统的防火墙和selinux:
防火墙:
service iptables stop //即时生效,重启后失效(stop/start)
chkconfig iptables off //启后生效(off/on)
service iptables status //查看防火墙的状态

not running

        selinux:            vi /etc/selinux/config                按i                    修改为SELINUX=disabled       //永久关闭,重启生效                ESC                :wq            命令行:                   setenforce 0                       //关闭SELinux,临时关闭            getenforce                         //查看selinux的区别                >>>>disabled   关闭                >>>>Enforcing  未关闭                >>>>        重启生效        reboot

3.连接测试

=====================================================SHELL基本使用========================================================================

5.SHELL基本使用(SHELL本质上是一个javaScript引擎!)
show dbs
查看当前服务实例上所有的数据库
db.getName()或者db
查看当前使用的数据库,也可以直接用db;db表示的是当前数据库
use 数据库名称
切换到指定数据库。如果数据库不存在,则创建数据库(此时只是在mongo的预处理缓存中,只有加入集合之后,这个数据库才会被创建,如果什么都不干就离开,这个数据库就会被删除)
db.dropDatabase()
删除当前使用数据库
db.repairDatabase()
修复当前数据库
db.version()
当前db版本
db.stats()
显示当前db状态
db.getMongo()
查看当前db的链接机器地址
db.serverStatus()
查看数据库服务器的状态

db.createCollection("集合名称")    创建集合show collections    查看当前数据库中所有的集合db.集合名称.drop()    删除集合(返回true表示删除成功!!)db.集合名称.find()    查询指定集合中所有的数据    可以通过 db.集合名称.find().pretty() 美化输出格式    默认是查询所有,可以通过:db.集合名称.find({查询条件}) 按条件查询集合中的数据    db.集合名称.findOne()  只会找到集合中的第一条数据!db.集合名称.insert({数据文档})  注意:BSON格式除了数据类型比json多一些之外,格式key/value对的形式和json都是一致的!    插入的每一条文档会自动帮我们生成一个_id字段,它是mongodb自动维护的,不需要我们关心db.集合名称.update({更新条件}, {要更新的字段})    更新指定集合数据,注意点,要更新的字段一定要这样写 {$set:{字段的名称:字段的值}}, $set修改器:只是修改某个字段!    注意:如果mongodb数据库中有多个符合条件的数据,mongodb默认只会修改第一条,当然可以批量修改,我们后面再说!    eg:db.persons.update({age:13},{$set:{age:18}})       //下面这样就会将整个对象都修改为lisi了!        var p=db.persons.findOne()        db.persons.update(p,{name:"lisi"})db.集合名称.remove({删除条件})        删除指定集合中的数据shell里面的help     数据库相关的help,db.help()     集合相关的help,db.集合名称.help()exit 退出当前操作cls 清屏注意点:更新和删除时一般都需要带条件,除非是全部更新与全部删除,不过全部更新与全部删除这样很危险,实际操作过程中很少telnet 127.0.0.1 27017
############################# 数据库和集合的命名规范 #############################################################33

1.数据库和集合的命名:可以是满足以下条件的任意UTF-8字符串。
不能是空字符串(”“)
不得含有’ ‘(空格)、.、/\\0()64admin,local,config开头

eg:因为mongo的shell是用的javascript,javascript的object的property的名称是数字的时候,不能用“.”来表示,所以你需要:db.getCollection('201405081400').renameCollection('exam');

2.有一些数据库名是保留的,可以直接访问这些有特殊作用的数据库。
admin: 从权限的角度来看,这是”root”数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。
local: 这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合
config: 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。

3.MongoDB的shell内置javascript引擎可以直接执行js代码
function insert(object){
db.getCollection(“graduate”).insert(object)
}
insert({age:32})
db.graduate.find()

############## MongoDB支持的数据类型

MongoDB 数据类型
下表为MongoDB中常用的几种数据类型。
数据类型 描述
String 字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的。
Integer 整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位。
Boolean 布尔值。用于存储布尔值(真/假)。
Double 双精度浮点值。用于存储浮点值。
Min/Max keys 将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比。
Arrays 用于将数组或列表或多个值存储为一个键。
Timestamp 时间戳。记录文档修改或添加的具体时间。
Object 用于内嵌文档。
Null 用于创建空值。
Symbol 符号。该数据类型基本上等同于字符串类型,但不同的是,它一般用于采用特殊符号类型的语言。
Date 日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息。
Object ID 对象 ID。用于创建文档的 ID。
Binary Data 二进制数据。用于存储二进制数据。
Code 代码类型。用于在文档中存储 JavaScript 代码。
Regular expression 正则表达式类型.

################# 文档(Document)数据的插入,删除和更新

1.插入文档:
db.[collectionName].insert({})
db.getCollection(“集合名”).insert()

2.批量插入文档
批量插入:1. db.[collectionName].insert([{},{}]) 数组对象的形式

          2.   还可以使用mongodb的应用驱动或者shell的for循环                shell本身就是一个javascript引擎,所以在shell中可以写任意js代码!                for(var i =0 ;i < 10 ;i++){                  db.graduate.insert({age:i})                 }                db.graduate.find()

3.save操作
save操作和insert操作区别在于当遇到_id相同的情况下
save变成更新操作
insert则会报错

 eg:db.graduate.insert({_id:"001",name:"hello"})     db.graduate.insert({_id:"001",name:"world"})     db.graduate.save({_id:"001",name:"world"})

1.删除集合中的所有数据
db.[collectionName].remove({})
db.[collectionName].drop()

  1. 根据条件删除
    db.[documentName].remove({})

    eg: db.graduate.remove({_id:”001”})

    当数据量十分庞大的时候,直接删除该集合并重新建立索引的办法要比直接用remove的效率高很多!

    更新:
    db.collection.update(
    {查询条件},
    {修改器},
    {
    upsert: ,
    multi: ,
    writeConcern:
    }
    )
    参数说明:
    query : update的查询条件,类似sql update查询内where后面的。
    update : update的对象和一些更新的操作符(如,inc…)等,也可以理解为sql update查询内set后面的
    upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
    multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。

    eg:db.graduate.update({‘title’:’MongoDB 教程’},{$set:{‘title’:’MongoDB’}})

1.强硬的文档替换式更新操作
db.[documentName].update({查询器},{修改器}) 注意:强硬的更新会用新文档替代老的文档!

    eg: db.graduate.insert({name:"haha",age:89})        db.graduate.find()        db.graduate.update({name:"haha"},{age:12})        db.graduate.        find()

2.主键冲突的时候会报错并且停止更新操作
eg:
db.graduate.insert({_id:”02”,age:12})
db.graduate.insert({_id:”03”,age:13})
db.graduate.update({age:12},{$set:{_id:”03”,age:15}})

3.insertOrUpdate操作:
db.graduate.insert({‘title’:’MongoDB 教程’,age:14})
db.graduate.update({“title”:’MongoDB 教程’},{“name”:”张三”},true)
db.graduate.find()
db.graduate.update({“title”:’MongoDB 程’},{“name”:”张三”},true)
db.graduate.find()

4.批量更新(批量更新必须和修改器配合使用,否则它是无法正常工作的!!)
以上语句只会修改第一条发现的文档,如果你要修改多条相同的文档,则需要设置 multi 参数为 true。
db.graduate.update({‘title’:’MongoDB 教程’},{$set:{‘title’:’MongoDB’}},{multi:true})

注意:默认情况下,当查询器查询出多条数据的时候默认只会修改第一条数据!注意:修改器一定要这样写 {$set:{字段的名称:字段的值}} 注意: $set修改器:只是修改某个字段!

5.使用修改器完成局部更新操作

修改器名称                 语法                          案例          $set                    {$set:{field:value}}     {$set:{name:"zhangsan"}}     它用来对符合条件的键值对,如果存在键就修改,不存在则就进行添加的操作! $inc                  {$inc:{field:value}}     {$inc:{age:1}}     只针对于数字类型,可以为符合条件的键所对应点数值进行加减操作! $unset                  {$unset:{field:1}}       {$unset:{name:1}}      它的用法很简单,就是删除指定的键 $push                   {$push:{field:value}}    {$push:{books:"JS"}}      如果指定的键是数组,则追加新的数值      如果指定的键不是数组则中断当前操作      如果不存在制定的键  则创建数组类型的键值对      eg:            db.graduate.drop()            db.graduate.insert({_id:1,name:"haha",books:[]})            db.graduate.find()            db.graduate.update({_id:1},{$push:{books:"age"}})            db.graduate.update({_id:1},{$push:{jobs:"age"}})            db.graduate.update({_id:1},{$push:{name:"age"}}) $pushAll                {$pushAll:{field:array}}     {$pushAll:{books:["EXTJS","JS"]}}              用法和$push相似,也可以理解为添加数组数据!            db.graduate.find()            db.graduate.update({_id:1},{$push:{books:["02","03","04"]}})             db.graduate.update({_id:1},{$pushAll:{books:["02","03","04"]}}) //与上面的对比 $addToSet               {$addToSet:{field:value}}    ${$addToSet:{books:"JS"}}     目标数组存在此项则不操作,不存在此项则加进去!         db.graduate.find()    db.graduate.insert({_id:5,books:["js"]})    db.graduate.update({_id:5},{$addToSet:{books:"js"}})    db.graduate.find()    db.graduate.update({_id:5},{$addToSet:{books:"sdf"}})    db.graduate.find()$pop                     {$pop:{field:value}}          {$pop:{name:1}}或{$pop:{name:-1}}     从指定数组删除一个值:1表示删除最后一个值,-1表示删除第一个数值!    eg:       db.graduate.update({_id:5},{$pop:{books:-1}})       db.graduate.update({_id:5},{$pop:{books:1}})$pull                     {$pull:{field:value}}            {$pull:{"book":"JS"}}     删除一个被指定的数值 eg:    1.先把值再加回来: db.getCollection('graduate').update({_id:5},{$addToSet:{books:"js"}})    2.db.getCollection('graduate').update({_id:5},{$pull:{books:"hah"}})    3.db.graduate.find()   $pullAll                  {$pullAll:{field:array}}          {$pullAll:{books:["js","hah"]}}    1.一次性删除多个指定的数值    eg:        db.getCollection('graduate').update({_id:5},{$addToSet:{books:"hello"}})        db.getCollection('graduate').update({_id:5},{$addToSet:{books:"hao"}})        db.getCollection('graduate').update({_id:5},{$pullAll:{books:["hao","js"]}})        db.graduate.find()注意:修改器是放在要更新对象的前面,查询器是写在内部哦,如下所示:!
########################################### find详解 ###########################################

首先将发给大家的persons.json文件在mongodb数据库中执行一下:

1.指定返回的键
use student
格式:db.[collectionName].find({条件},{键指定})
eg:查询出所有数据的指定键(name,country)
db.persons.find({},{_id:0,name:1,country:1}) 注意:第一个参数为空对象,表示查询全部的值!后面的这个对象中的_id:0表示不要这个字段,name:1表示要查这个字段,
那么最后mongodb只返回name,country两个字段,其它不写的不返回,但是如果不写_id那么mongodb默认是会返回_id的!

2.查询条件
比较操作符

$lt       <        {age:${$gt:22,$lte:27}}   less than$lte      <=                                 less than or equal$gt       >                                  greater than$gte      >=$ne       !=        {age:{$ne:26}}eg:查询出年龄在22到27岁之间的学生    eg:    db.persons.find({age:{$gte:22}},{_id:0,name:1,age:1})    db.persons.find({age:{$lte:27}},{_id:0,name:1,age:1})    db.persons.find({age:{$gte:22,$lte:27}},{_id:0,name:1,age:1})  //注意:如果查询条件是与的关系,那么可以在一个对象中写就可以了,中间用逗号(,)隔开!  查询出国籍 不是韩国国籍的学生:    db.persons.find({country:{$ne:"Korea"}},{_id:0,name:1,country:1}).pretty()

3.包含或不包含
innin (无论是in还是nin,它们后面紧跟的是数组,而不能作用于其它对象!)

   案例:查询出国籍是中国或者美国的学生信息     db.persons.find({country:{$in:["Korea","USA"]}},{_id:0,name:1,country:1}).pretty()         查询出国籍不是中国或者美国的学生信息     db.persons.find({country:{$nin:["Korea","USA"]}},{_id:0,name:1,country:1}).pretty()

4.OR查询

案例:查询出语文成绩大于80或者英语成绩大于90的学生信息.      db.persons.find({$or:[{c:{$gt:80}},{e:{$gt:90}}]},{_id:0,name:1,c:1,e:1})  //注意:$or后面跟的是一个数组,这个数组里面是或的对象!

5.AND条件查询

MongoDB 的 find() 方法可以传入多个键(key),每个键(key)以逗号隔开,及常规 SQL 的 AND 条件db.persons.find({key1:value1, key2:value2},{_id:0,name:1}).pretty()eg:查询出语文成绩在80到90之间的学生信息   db.getCollection('persons').find({c:{$gt:80,$lt:90}},{_id:0,name:1,c:1})   db.getCollection('persons').find({c:{$gt:80},e:{$gt:70}},{_id:0,name:1,c:1,e:1})

6.AND 和 OR 联合使用
eg:查询出语文成绩大于80,并且 是英语成绩大于60或者数学成绩小于90的学生信息!

    db.getCollection('persons').find({c:{$gt:80},$or:[{e:{$gt:60}},{m:{$lt:90}}]},{_id:0,name:1,c:1,e:1,m:1})

7.查询集合中的文档 ,统计(count)、排序(sort)、分页(skip、limit)
db.persons.count();
db.persons.find().count();
db.persons.find({age:{$gt:25}}).count()

db.persons.find().sort({age:1});//在MongoDB中使用使用sort()方法对数据进行排序,sort()方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而-1是用于降序排列。语法:db.COLLECTION_NAME.find().sort({KEY:1})MongoDB Limit() 方法如果你需要在MongoDB中读取指定数量的数据记录,可以使用MongoDB的Limit方法,limit()方法接受一个数字参数,该参数指定从MongoDB中读取的记录条数。语法:db.COLLECTION_NAME.find().limit(NUMBER)db.persons.find().limit(3)db.persons.find({age:{$gt:25}}).limit(3)//也可以先指定查询条件再限制取几条!MongoDB Skip() 方法我们除了可以使用limit()方法来读取指定数量的数据外,还可以使用skip()方法来跳过指定数量的数据,skip方法同样接受一个数字参数作为跳过的记录条数。skip() 方法脚本语法格式如下:    db.COLLECTION_NAME.find().skip(NUMBER.limit(NUMBER).skip(NUMBER)    eg:db.persons.find().skip(1).limit(3)db.persons.find().sort({age:-1}).skip(2).limit(3);   //db.persons.find().sort({age:-1}).skip(2).limit(3).count(); //count默认就是0db.persons.find().sort({age:-1}).skip(2).limit(3).count(0); //0代表false,1代表true(非0值都是true),false就是忽略前面的sort({age:-1}).skip(2).limit(3);true代表不忽略db.persons.find().sort({age:-1}).skip(2).limit(3).count(1);
##################################### 索引

1.MongoDB 索引简介

索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构索引就是用来加速查询的。数据库索引与书籍的索引类似:有了索引就不需要翻遍整本书,数据库则可以直接在索引中查找,使得查找速度能提高几个数量级。在索引中找到条目以后,就可以直接跳转到目标文档的位置。

2.先来个性能测试:

    先给数据库插入20万数据    for(var i = 0 ; i<200000 ;i++){        db.books.insert({number:i,name:i+"book"})    }    1.先检验一下查询性能    var start = new Date()    db.books.find({number:5871})    var end = new Date()    end - start    2.为number 创建索引         db.books.ensureIndex({number:1})    3.再执行第一部的代码    var start = new Date()    db.books.find({number:5871})    var end = new Date()    end - start    从上面可以看出:当建立索引之后,我们可以看出有数量级的性能提升

3.
1)创建索引:
MongoDB使用 ensureIndex() 方法来创建索引。
格式:db.COLLECTION_NAME.ensureIndex({KEY:1})
db.col.ensureIndex({“title”:1,”description”:-1})
ensureIndex() 方法中你也可以设置使用多个字段创建索引(关系型数据库中称作复合索引)。

   注意:1.语法中 Key 值为你要创建的索引字段,1为指定按升序创建索引,如果你想按降序来创建索引指定为-1即可。(正序与倒序主要是针对创建时间而言的!如:创建最近一个销售情况,这个时候可以建立倒序索引!)         2.索引创建在提高查询性能的同时,同时也会影响插入的性能,对于经常查询少插入的文档可以考虑使用建立索引的方式!         3.同时也要注意复合索引的创建顺序         4.在排序的时候如果是超大数据量,也可以考虑加上索引来提高排序的性能!         5.可以用我们的客户端查看索引的名称,_id是mongodb都会默认给我们的文档加上的字段,并且会为其分配索引_id_,其命名规则是:字段名_1(-1),其中_1表示正序排列,_-1表示倒序排列!注意:ensureIndex() 接收可选参数,可选参数列表如下:Parameter                                  Type                             Descriptionbackground                               Boolean             建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 "background" 可选参数。 "background" 默认值为false。unique                                   Boolean             建立的索引是否唯一。指定为true创建唯一索引。默认值为false.name                                     string              索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。dropDups                                 Boolean             在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为 false.v                                      index version         索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。weights                                  document            索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。

2)在创建索引的时候我们还可以指定索引名称!
eg: db.books.ensureIndex({name:-1},{name:”bookname”}) //前提是数据库中有这个name字段!

3)唯一索引

  如何解决文档books不能插入重复的数值  建立唯一索引:   eg:        删除索引(否则不可以建立唯一索引!!!)        //mongodb是不支持对同一个字段建立多个索引的!         db.books.ensureIndex({name:-1},{unique:true})        db.books.insert({name:"1book"})        db.books.ensureIndex({number:1},{background:true})   

4) 删除唯一索引!

        db.books.insert({name:"1book"})        db.getCollection('books').find({name:"1book"})        db.books.ensureIndex({name:-1},{unique:true})  //测试不能通过,因为有重复数据!        db.books.ensureIndex({name:-1},{dropDups:true}) //在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为 false.5)  db.books.ensureIndex({number:1},{background:true}) //在后台执行,这样就可以解决创建索引的时候会暂时锁表的问题!

4.Hint
如何强制查询使用指定的索引呢?
db.books.find({name:”1book”,number:1}).hint({name:-1})

   注意:指定索引必须是已经创建了的索引!

5.explain 查看查询使用索引的情况
db.books.find({name:”1book”}).explain()

6.删除索引
批量和精确删除索引
db.runCommand({dropIndexes:”books”,index:”name_-1”}) //注意:dropIndexes对应的值为集合的名称!
db.runCommand({dropIndexes:”books”,index:”*”})

################################## 固定集合

1.固定集合(capped collection)
固定集合指的是事先创建而且大小固定的集合 。

固定集合特性:固定集合很像环形队列,如果空间不足,最早的文档就会被删除,为新的文档腾出空间。一般来说,固定集合适用于任何想要自动淘汰过期属性的场景,没有太多的操作限制。创建固定集合使用命令:db.createCollection(“collectionName”,{capped:true,size:100000,max:100});size指定集合大小,单位为KB,max指定文档的数量当指定文档数量上限时,必须同时指定大小。淘汰机制只有在容量还没有满时才会依据文档数量来工作。要是容量满了,淘汰机制会依据容量来工作。当然也可以把一个普通集合转换为固定集合:db.runCommand({"convertToCapped":"books",size:100000})

2.mongoDB数据库的关闭:
1.先切换到admin数据库
2.然后在本地shell中admin数据库中执行 db.shutdownServer()

温馨提示:如果不切换到admin数据库直接关闭mongodb服务器会提示:shutdown command only works with the admin database; try ‘use admin’
如果不在本地shell执行又没有用用户登录的情况下同样会提示:Error:”shutdown must run from localhost when running db without auth”,

============================== 导入(mongoimport)和导出(mongoexport) :针对集合 ===============================

1.导出数据可以使用命令mongoexport:(中断一切增删改查操作)
Mongodb中的mongoexport工具可以把一个collection导出成JSON格式或CSV格式的文件。

格式:mongoexport -h dbhost -d dbname -c collectionName -o output

参数说明:-h  数据库地址-d 指明使用的库-c 指明要导出的集合-o 指明要导出的文件名-f  指明要导出哪些列(也可以指明导出的列,列与列之间用逗号隔开!)在导出数据时没有显示指定导出样式 ,默认导出了JSON格式的数据。如果我们需要导出CSV格式的数据,则需要使用--csv参数。导出的位置在/usr/lib/mongodb/bin下面,可以自己指定路径eg: mongoexport -d student -c graduate -f name,age --csv -o /opt/output   注意:csv格式的数据都是以逗号分隔的(,)!注意:当导出其它机子上的数据的时候还要指定ip和端口号:      -h, --host=<hostname>      --port=<port>     具体的命令参数可以查看:mongoexport --help

2.导入数据可以使用命令 mongoimport:(中断一切增删改查操作)

Mongodb中的mongoimport工具可以把一个特定格式文件中的内容导入到指定的collection中。该工具可以导入JSON格式数据,也可以导入CSV格式数据。mongoimport -h dbhost -d dbname -c collectionname --file 文件的地址...eg: mongoimport -d student -c persons --file people.json参数说明:-h  数据库地址-d 指明使用的库-c 指明要导入的集合--file:指定本地的文件地址...
################ 运行时备份(mongodump)和运行时恢复(mongorestore) : 针对数据库
MongoDB提供了运行时备份和运行时恢复的功能,这种方式不会中断增删改查操作但是可能会漏掉一部分数据!分别是MongoDB下载目录下的mongodump.exe和mongorestore.exe文件

1.备份数据使用下面的命令:
mongodump -h dbhost -d dbname -o dbdirectory

-h:MongDB所在服务器地址,例如:127.0.0.1,当然也可以指定端口号:127.0.0.1:27017-d:需要备份的数据库实例,例如:test-o:备份的数据存放目录位置,例如:c:\data\dump,当然该目录需要提前建立,在备份完成后,系统自动在dump目录下建立一个test目录,这个目录里面存放该数据库实例的备份数据。eg:导出本机上的27017端口号mongodb下的student数据库!    mongodump -h 127.0.0.1:27017 -d student -o /opt/test/

2.恢复数据使用下面的命令:
mongorestore -h dbhost -d dbname dbdirectorypath

-h:MongoDB所在服务器地址-d:需要恢复的数据库实例,例如:test,当然这个名称也可以和备份时候的不一样,比如test2dbdirectorypath:备份数据所在位置(指定的数据库的目录),例如:c:\data\dump\test实例:删除原本的数据库,然后用刚才导出的数据库恢复!    db.dropDatabase()    mongorestore -h 127.0.0.1:27017 -d student  /opt/test/student

================================================ mongodb主从复制和副本集 ===========================================

1.MongoDB 复制(副本集)
MongoDB主从复制是将数据同步到多个从服务器的过程。
复制提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性, 并可以保证数据的安全性。

2.MongoDB复制原理

mongodb的复制至少需要两个节点。其中一个是主节点,负责处理客户端请求,其余的都是从节点,负责复制主节点上的数据。mongodb各个节点常见的搭配方式为:一主一从、一主多从。主节点记录在其上的所有操作oplog,从节点定期轮询主节点获取这些操作,然后对自己的数据副本执行这些操作,从而保证从节点的数据与主节点一致。参考我们发的资料中的:MongoDB主从复制原理!注意:1)、在数据库集群中要明确的知道谁是主服务器,主服务器只有一台!      2)、从服务器要知道自己的数据源也就是自己要备份哪一台服务器      3)、--master用来确定主服务器,--slave和--source来控制从服务器!                                              --source就是来告诉从服务器谁是你的主服务器!

3.一主一从:
主服务器配置:
dbpath=/opt/installDir/mongodb3-2/data 主数据库地址
port=8888 主数据库端口号
bind_ip=127.0.0.1 主数据库所在服务器( 绑定服务IP,若绑定127.0.0.1,则只能本机访问,不指定默认本地所有IP)
master=true 确定自己是主服务器

从服务器
dbpath=dbpath=/opt/installDir/mongodb3-1/data 从数据库地址
port=7777 从数据库端口号
bind_ip=127.0.0.1 从数据库所在服务器
source=127.0.0.1:8888 确定主数据库端口
slave=true 确定自己是从服务器

先启动主服务器:mongod --dbpath=/opt/installDir/mongodb3-2/data --logpath=/opt/installDir/mongodb3-2/logs  --logappend  --port=10000 --master再启动从服务器:mongod --dbpath=/opt/installDir/mongodb3-2/data01 --logpath=/opt/installDir/mongodb3-2/logs01  --logappend  --port=10001  --slave --source=localhost:10000mongo localhost:10000  //进入主服务器插入一条数据在从服务器上执行:rs.slaveOk();

启动成功后就可以连接主节点进行操作了,而这些操作会同步到从节点。

4.主从复制的其他设置项
–only 从节点指定复制某个数据库,默认是复制全部数据库
–slavedelay 从节点设置主数据库同步数据的延迟(单位是秒)
–autoresync 从节点如果不同步则从新同步数据库,如果不设置这一项,那么只会从当前时间开始往后复制,此时间以前的数据将不再管了!
–oplogSize 主节点设置oplog的大小(主节点操作记录存储到local的oplog中,oplog这个日志文件中记录的是主数据库的增删改操作)

1.副本集
:副本集就是有自动故障恢复功能的主从集群。

主从集群和副本集最大的区别就是副本集没有固定的“主节点”;整个集群会选出一个“主节点”,当其挂掉后,又在剩下的从节点中选中其他节点为“主节点”,副本集总有一个活跃点(primary)和一个或多个备份节点(secondary)。以三个节点为例:    节点1:        HOST:localhost:10001        Log File: /opt/installDir/mongodb3-2/logs        Data File:/opt/installDir/mongodb3-2/data    节点2:        HOST:localhost:10002        Log File: /opt/installDir/mongodb3-2/logs01        Data File:/opt/installDir/mongodb3-2/data01    节点3:        HOST:localhost:10003        Log File: /opt/installDir/mongodb3-2/logs02        Data File:/opt/installDir/mongodb3-2/data02启动节点1:mongod --dbpath=/opt/installDir/mongodb3-2/data --logpath=/opt/installDir/mongodb3-2/logs --logappend --port=10001 --replSet="child/localhost:10002"mongod --dbpath /opt/installDir/mongodb3-2/data --logpath /opt/installDir/mongodb3-2/logs --logappend --port 10001 --replSet child/localhost:10002注意:--replSet用于设置同伴!启动节点2:mongod --dbpath /opt/installDir/mongodb3-2/data01 --logpath /opt/installDir/mongodb3-2/logs01 --logappend --port 10002 --replSet child/localhost:10001启动节点3:  mongod --dbpath /opt/installDir/mongodb3-2/data02 --logpath /opt/installDir/mongodb3-2/logs02 --logappend --port 10003 --replSet child/localhost:10001,localhost:10002

(只能初始化一次):随便登录一个节点,以10001为例
mongo localhost:10001/admin
db.runCommand({
“replSetInitiate”:{
“_id”:”child”,
“members”:[
{
“_id”:1,
“host”:”localhost:10001”,
“priority”:3
},
{
“_id”:2,
“host”:”localhost:10002”,
“priority”:2
},
{
“_id”:3,
“host”:”localhost:10003”,
“priority”:1
}
]
}
});

查询当前主库,登录10002mongo localhost:10002db.$cmd.findOne ( {ismaster: 1 } );关闭10001服务Dos命令窗口,  登录10002查询当前主库mongo localhost:10002db.$cmd.findOne ( {ismaster: 1 } );

三个节点的副本集,如果停止第一个节点(master),第二个节点变为主节点(master),第三个节点还是从节点。
如果把第二个节点也停止掉,第三个节点不会变为主节点,原因可以查看下一个页面。
https://segmentfault.com/q/1010000000532867

副本集注意点:
这取决于你的副本集总的节点个数,副本集中可以相互联系的节点数大于总节点数一半时,可以有新的
primary节点被选取出来,副本集可以正常工作,如果可以相互联系的节点小于等于总节点一半,集群不可工作!

======================================== 用户管理和安全认证 =========================================================

0.mongoDB数据库设置用户名及密码
MongoDB数据库在默认是没有用户名及密码,不用安全验证的,只要连接上服务就可以进行CRUD操作。

1.每个MongoDB实例中的数据库都可以有许多用户。如果开启了安全性检查,则只有数据库认证用户才能执行读或者写操作。
admin数据库中的用户被视为超级用户(即管理员)。也就是说位于admin库的账号是全局的,其他库的账号都是在本库有效。在认证之后,管理员可以读写所有数据库,执行特定的管理命令,如listDatabases和shutdown。

在开启安全检查之前,一定要至少有一个管理员账号。

注意一点,帐号是跟着库走的,所以在指定库里授权,必须也在指定库里验证(auth)。因为在admin下面添加的帐号,所以要到admin下面验证。

第一步:开启安全性验证
如果需要给MongoDB数据库使用安全验证,则需要用–auth开启安全性检查,则只有数据库认证的用户才能执行读写操作,开启安全性检查,有两种方式:
带有-auth参数时,必须通过认证才可以查询数据。如果没有加-auth参数,即使配置了安全认证用户,也不需要认证谁都可以操作。

mongod --dbpath=/opt/installDir/mongodb3-2/data --logpath=/opt/installDir/mongodb3-2/logs --logappend --port=27017 --fork --s

第二步:创建用户
使用mongo.exe进入mongodb的命令行管理。
键入命令:
如果你要为admin数据库,设置一个用户,使用如下命令:
use DB名称

db.createUser(   {     user: "账号名",     pwd: "密码",     roles: [ { role: "角色", db: "DB名称" }]   })注意:1.创建一个数据库新用户用db.createUser()方法,如果用户存在则返回一个用户重复错误。      2.至于不同角色的权限区别,这个只能对着官网去看了,毕竟角色太多了      Built-In Roles(内置角色):        1. 数据库用户角色:read、readWrite;        2. 数据库管理角色:dbAdmin、dbOwner、userAdmin;        3. 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager;        4. 备份恢复角色:backup、restore;        5. 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase        6. 超级用户角色:root         // 这里还有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、userAdminAnyDatabase)        7. 内部角色:__system        具体角色:        Read:允许用户读取指定数据库        readWrite:允许用户读写指定数据库        dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile        userAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户        clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。        readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限        readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限        userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限也就是说:userAdminAnyDatabase 权限只是针对用户管理的,对其他是没有权限的。        dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。        root:只在admin数据库中可用。超级账号,超级权限  //也就是说root权限不仅可以授权,而且也可以对集合进行任意操作,只是不建议使用。那就是role角色设置成root,凡是权限为root的用户都是超级用户。        刚建立了 userAdminAnyDatabase 角色,用来管理用户,可以通过这个角色来创建、删除用户。eg:use admindb.createUser({    user: "dba",    pwd: "dba",    roles: [ { role: "userAdminAnyDatabase", db: "test" } ]    })   //这个会报错,因为只有admin数据库下的账号才可以用userAdminAnyDatabase权限!    db.system.users.find()//上面这个userAdminAnyDatabase角色只是针对用户的集合我们可以查看操作,但是对于其它集合我们是没有权限的!db.createUser({    user: "abc",    pwd: "dba",    roles: [ { role: "readWrite", db: "test" } ]    })db.createUser({    user: "dba",    pwd: "dba",    roles: [ { role: "read", db: "test" } ]    })db.createUser({    user: "root",    pwd: "root",    roles: [ { role: "root", db: "admin" } ]    })    //注意:如果不在admin数据库下,是不能赋予root权限的!    use test    show collections    db.books.insert({name:"hah"})

第三步:用户登录:
用–auth开启安全性检查,启动mongodb服务!
mongod –dbpath=/opt/installDir/mongodb3-2/data –logpath=/opt/installDir/mongodb3-2/logs –logappend –auth –port=27017 –fork
首先需要使用“use 数据库名称,如: use admin”跳转到当前数据库模式下,然后用户登录,如下:
db.auth(“root”,”root”)
上面1表示登录成功,0表示登录失败。

system.user用户(查看所有账号,可以通过db.system.users.find().pretty()这个命令!)
每个数据库的用户账号都是以文档形式存储在system.users集合里面的,
文档结构{‘user’:username, ‘readOnly’:true, ‘pwd’: password hash}。password hash是根据用户名和密码生成的散列。

查询某个数据库下的用户db.system.users.find();
删除某个数据库下的所有用户db.system.users.remove(),删除指定用户db.system.users.remove({‘user’:’用户名’})

//验证: 注意一点,帐号是跟着库走的,所以在指定库里授权,必须也在指定库里验证(auth)。因为在admin下面添加的帐号,所以要到admin下面验证。

    退出shell客户端,重新登陆mongo Shell客户端    然后切换到test数据库    use test    show collections //会保错,not authorized on test to execute command    db.auth("dba","dba") //Authentication failed.授权失败,原因是我们的dba用户是在admin数据库中创建的,这就需要我们切换到admin数据库中验证,也就是帐号是跟着库走的,所以在指定库里授权,必须也在指定库里验证(auth)!    use admin    db.auth("dba","dba")//验证,然后切换到test数据库!    use test    show collections    db.books.find()总结:上面更加进一步说明数据库帐号是跟着数据库来走的,哪里创建哪里认证。创建的帐号,不能直接在其他库验证,只能在帐号创建库下认证,再去其他库进行操作。从普通用户切换到admin数据库,要查看用户信息的时候需要重新用root权限验证;而root用户切换到普通数据库不需要权限验证就可以使用!好了,现在我们已经为mongodb设置了一个全局用户root,接下来先重启mongodb,使创建的用户生效。在admin数据库中关闭mongodb服务!注意:在特权用户root下,admin数据库下的用户是超级用户。      show dbs  //查看当前mongodb数据库所有数据库信息:      上面显示所有数据库名称,数据库大小。

接着验证:
use test #在test库里创建帐号
db.createUser(
{
user: “zjyr”,
pwd: “zjyr”,
roles: [
{ role: “read”, db: “test” } #只读帐号
]
}
)

db.createUser(    {        user: "zjyr",        pwd: "zjyr",        roles: [        { role: "read", db: "test" }        ]    })    db.createUser(        {            user: "zxy",            pwd: "zxy",            roles: [            { role: "readWrite", db: "test" }  //读写都可以!            ]        }    )    db.createUser(        {            user: "zxy",            pwd: "zxy",            roles: [            { role: "readWrite", db: "test" }            ]        }    )exit   mongo 进入mongo SHELL客户端!  use test  db.auth('zjyr','zjyr')       #用创建的readWrite帐号进行写入  db.abc.insert({"a":1,"b":2})  db.abc.insert({"a":11,"b":22})  db.abc.insert({"a":111,"b":222})  db.abc.find()  db.auth('zxy','zxy')       #切换到只有read权限的帐号  db.abc.insert({"a":1111,"b":2222})  #不能写入  db.abc.find()        #可以查看

最后:备份还原使用那个角色的帐号?之前创建的帐号zjy:test库读写权限;zjyr:test库读权限

   mongodump --port=27017 -u zjyr -p zjyr --db=test -o backup   #只要读权限就可以备份   mongorestore --port=27017 -u zxy -p zxy --db=test /opt/test/backup/test  #读写权限可以进行还原

=================================================== 分片(sharding)分布式存储(当表的数据很多的时候,TB级别的数据!)===========================================

需求分析:
当MongoDB存储海量的数据时,一台机器可能不足以存储数据,也可能不足以提供可接受的读写吞吐量。这时,我们就可以通过在多台机器上分割数据,使得数据库系统能存储和处理更多的数据。
为何要分片?
1.减少单机请求数,降低单机负载,提高总负载
2.减少单机的存储空间,提高总存空间。

1.分片(sharding)是指将数据拆分,将其分散存在不同的机器上的过程。有时也用分区(partitioning)来表示这个概念。
将数据分散到不同的机器上,不需要功能强大的大型计算机就可以储存更多的数据,处理更多的负载。
MongoDB分片的基本思想就是将集合切分成小块。这些块分散到若干片里面,每个片只负责总数据的一部分。应用程序不必知道哪片对应哪些数据,甚至不需要知道数据已经被拆分了,所以在分片之前要运行一个路由进程,该进程名为mongos。
这个路由器知道所有数据的存放位置,所以应用可以连接它来正常发送请求。对应用来说,它仅知道连接了一个普通的mongod。路由器知道数据和片的对应关系,能够转发请求到正确的片上。如果请求有了回应,路由器将其收集起来回送给应用。

2.片键
设置分片时,需要从集合里面选一个键,用该键的值作为数据拆分的依据。这个键称为片键(shard key)。
{name:”zhangsan”,age:1}
用个例子来说明这个过程:假设有个文档集合表示的是人员。如果选择名字(“name”)作为片键,第一片可能会存放名字以A~F开头的文档,
第二片存的G~P的名字,
第三片存的Q~Z的名字。
随着添加或者删除片,MongoDB会重新平衡数据,使每片的流量都比较均衡,数据量也在合理范围内。
利用路由可以实现数据的物理切分,逻辑不切分!

2.参考PPT资料架构图讲解!
路由,客户端由此接入,且让整个集群看上去像单一数据库,前端应用可以透明使用

3.步骤:
1、创建三个目录,分别存放两个mongod服务的数据文件和config服务的数据文件
mkdir mongod_node1; mkdir mongod_node2;mkdir config_node

2、开启config服务器 。mongos要把mongod之间的配置放到config服务器里面,所以首先开启它,这里就使用2222端口。 命令为:
mongod –dbpath /opt/installDir/mongodb3-2/config_n
ode –port 2222 –configsvr //启动Config Server用的是configsvr命令

   注意:1.配置服务器用的是普通的mongod这个命令:

3、开启mongos服务器 。这里要注意的是我们开启的是mongos,端口3333,同时指定下config服务器。命令为:
mongos –port 3333 –configdb=127.0.0.1:2222 –chunkSize=1

    注意:    1.路由器是调用mongos命令,路由服务器要监听谁是配置服务器    2.--chunkSize=1,指的是节点的存储大小为1M.    3.如果是多台configsvr服务器,那么我们这里只要在--configdb这里将多台服务器用逗号隔开就好!

4、启动mongod服务器 。对分片来说,也就是要添加片了,这里开启两个mongod服务,端口分别为:4444,5555。命令为:

    mongod --dbpath /opt/installDir/mongodb3-2/mongod_node1 --port  4444            //可以指定--shardsvr,表明这是一个shard分片服务器    mongod --dbpath /opt/installDir/mongodb3-2/mongod_node2 --port  55555、服务配置 。client直接跟mongos打交道,也就说明我们要连接mongos服务器,然后将4444,5555的mongod交给mongos,添加分片也就是addshard()。   mongo 127.0.0.1:3333/admin   db   db.runCommand({"addshard":"127.0.0.1:4444",allowLocal:true}) //利用路由服务器为集群添加分配并允许本地访问!   db.runCommand({"addshard":"127.0.0.1:5555",allowLocal:true})  //allowLocal:允许本地访问  切记: 在这之前不要使用任何数据库语句,否则会出错的!  开启数据库分片功能,命令很简单 enablesharding(),这里就开启test数据库。    db.runCommand({"enablesharding":"test"})          //到admin中设置test数据库支持分片,test数据库是业务数据库,默认是不打开分配功能的!  指定集合中分片的片键,这里就指定为person.name键。   //到admin中设置该集合支持分片同时为了将一个特定的collection存储在多个shard中,需要为该collection指定一个shard key(片键),   db.runCommand({"shardcollection":"test.person","key":{"name":1}}) 通过mongos插入10w记录,然后通过printShardingStatus命令查看mongodb的数据分片情况。  use test       for(var i=0 ;i <100000;i++) {db.person.insert({name:"bawei"+i})}  db.printShardingStatus() //可以查看配置库对于分片的分配!  db.person.find().count()  mongo 127.0.0.1:4444/admin  //单独进入到这里面查看数据的!  mongo 127.0.0.1:5555/admin  综上:,客户端由前端路由接入,然后询问Config Servers需要到哪个Shard上查询或保存记录,再连接相应的Shard进行操作,最后将结果返回给客户端。         客户端只需要将原本发给mongod的查询或更新请求原封不动地发给Routing Process,而不必关心所操作的记录存储在哪个Shard上。(所有操作在mongos上操作即可)

注意这里我们要注意片键的选择,选择片键时需要根据具体业务的数据形态来选择,切不可随意选择,实际中尤其不要轻易选择自增_id作为片键,除非你很清楚你这么做的目的,
具体原因我不在此分析,根据经验推荐一种较合理的片键方式,“自增字段+查询字段”,没错,片键可以是多个字段的组合。
另外这里说明一点,分片的机制:mongodb不是从单篇文档的级别,绝对平均的散落在各个片上, 而是N篇文档,形成一个块”chunk”,优先放在某个片上,
当这片上的chunk,比另一个片的chunk区别比较大时(>=3) ,会把本片上的chunk,移到另一个片上, 以chunk为单位,维护片之间的数据均衡。
也就是说,一开始插入数据时,数据是只插入到其中一块分片上的,插入完毕后,mongodb内部开始在各片之间进行数据的移动,这个过程可能不是立即的,
mongodb足够智能会根据当前负载决定是立即进行移动还是稍后移动。

=============================利用 JavaAPI 来操作MongoDB数据库 =======================================
Java集成mongodb有很多方式,可以直接用mongodb的java驱动程序来发送语句到mongodb服务器

多为前辈指点,标注为转载。

个人浅解,如有不对的地方,希望多多指点。
侵删。

原创粉丝点击