MongoDB基础

MongoDB的安装和启动,MongoDB启动默认使用/data/db路径作为数据存放路径,也可通过--dbpath参数指定其他路径,若未创建则启动报错,通过--auth参数以授权模式启动,通过--logpath参数设置日志文件存储路径。

MongoDB基于安全性考虑,默认安装后只会绑定本地回环IP127.0.0.1,可通过启动服务时--bind_ip绑定IP。绑定后登陆时也需要通过该IP登陆。

1
2
3
4
5
6
7
8
9
10
11
12
13
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-4.4.2.tgz
tar -xvzf mongodb-linux-x86_64-rhel70-4.4.2.tgz
mkdir -p /data/db # 该路径是MongoDB默认的数据存放路径
# --auth以授权模式启动,--logpath指定日志文件,--dbpath指定数据存放路径,若省略默认使用/data/db路径
bin/mongod --auth --logpath /data/db/logpath/output --dbpath /data/db --bind_ip 192.168.109.200 --fork
# 登陆 若是本机--host和--port可省略
bin/mongo -host 192.168.109.200 -u eleven
# 设置密码,登陆mongo后
use admin # 切换数据库到admin
db.createUser({user: "eleven", pwd: "eleven", roles: ["root"]}) # 创建用户
show users # 查看所有用户信息
db.shutdownServer() # 停掉服务
exit # 退出mongo

MongoDB基本操作

writeConcern定义了本次文档创建操作的安全写级别安全写级别用来判断一次数据库写入操作是否成功,安全写级别越高,丢失数据的风险就越低,但写入操作延迟也可能更高。writeConcern决定一个写操作落到多少个节点上才算成功。 发起写操作的程序将阻塞到写操作到达指定的节点数为止:

  • 0:发起写操作,不关心是否成功
  • 1- 集群中最大数据节点数:写操作需要被复制到指定节点数才算成功
  • majority:写操作需要被复制到大多数节点上才算成功,过半机制

插入文档时若没有显示指定主键,MongoDB将默认创建一个主键,字段固定为_idObjectId()可快速生成12字节id作为主键,ObjectId前四个字节代表主键生成时间精确到秒。主键ID在客户端驱动生成,一定程度上代表了顺序性但不保证顺序性, 可通过ObjectId("id值").getTimestamp()获取创建时间。

1
2
3
4
# insertOne和insertMany命令不支持explain命令
db.collection.insertMany([{<JSON对象1>},{<JSON对象2>}],{writeConcern: 安全级别,ordered: true/false}) # 批量添加文档
db.collection.insert(<JSON对象>, {writeConcern: 安全级别}) # 添加单个文档
db.collection.insertOne(<JSON对象>, {writeConcern: 安全级别}) # 添加单个文档

ordered决定是否按顺序写入,顺序写入时,一旦遇到错误,便会退出,剩余的文档无论正确与否,都不会写入,乱序写入,则只要文档可正确写入,不管前面的文档是否是错误的文档。

1
2
3
4
5
6
7
8
9
db.collection.find({})               # 查询所有的文档 
db.collection.find({}).pretty() # 返回格式化后的文档
db.collection.find({qty:0, status:"D"}) # 精准等值查询
db.collection.find({"size.uom": "in"}) # 嵌套对象精准查询
# 返回指定字段,默认会返回_id字段,同样可以通过指定_id:0,剩余字段不能混合指定
db.collection.find({}, {item: 1, status: 1})
# and和or条件查询,以及$in,$nin,$exists
db.collection.find({$and:[{"qty":0},{"status":"A"}]}).pretty()
db.collection.find({$or:[{"qty":0},{"status":"A"}]}).pretty()

复合主键,若字段顺序变换即使内容完全一致,也会当做不同的对象被创建

1
db.demeDoc.insert({_id: {product_name: 1, product_type: 2}, supplierId: "001", create_Time: new Date()})

逻辑操作符匹配

  • $not:匹配筛选条件不成立的文档
  • $and:匹配多个筛选条件同时满足的文档
  • $or:匹配至少一个筛选条件成立的文档
  • $nor:匹配多个筛选条件全部不满足的文档
1
2
3
4
5
db.members.find({points: {$not: { $lt: 100}}});
db.members.find({$and: [{nickName:{$eq : "曹操"}}, {points:{$gt: 1000}}]});
db.members.find({nickName:{$eq: "曹操"}, points:{$gt: 1000}}); # 当作用在不同字段上可省略 $and
db.members.find({points:{$gte:1000, $lte:2000}}); # 当作用在同一字段时可简化
db.members.find({$or: [{nickName:{$eq : "曹操"}}, {points:{$gt: 1000}}]});

默认count()不考虑skiplimit效果,若希望考虑limit和skip,需要设置为true。 分布式环境下count不保证数据绝对正确。当同时应用sort,skip,limit时,应用顺序为sortskiplimit。可使用$slice返回数组中部分元素,可使用$elementMatch进行数组元素匹配

1
2
3
4
5
db.members.find().skip(1).limit(10).count();
db.members.find().skip(1).limit(10).count(true);
db.members.find().sort({field: 1/-1}); # 排序,1:顺序,-1:逆序
db.members.find({},{_id:0, nickName:1, points:1, address: {$slice:1}}); # 返回第一个元素
db.members.find({},{_id:0, nickName:1, points:1, tag: {$elemMatch: {$eq: "00"}}});

投影设置:{field: <1:1表示需要返回,0:表示不需要返回,只能为0或1,非主键字段不能同时混选0或1>}

1
db.members.find({},{_id:0, nickName:1, points:1})

更新操作updateOne/updateMany方法要求更新条件部分必须具有以下之一,否则将报错

  • $set 给符合条件的文档新增一个字段,有该字段则修改其值
  • $unset 给符合条件的文档,删除一个字段
  • $push: 增加一个对象到数组底部
  • $pop:从数组底部删除一个对象
  • $pull:如果匹配指定的值,从数组中删除相应的对象
  • $pullAll:如果匹配任意的值,从数据中删除相应的对象
  • $addToSet:如果不存在则增加一个值到数组
  • $rename:重命名字段
  • $inc:加减字段值
  • $mul:相乘字段值
  • $min:采用最小值
  • $max:次用最大值

默认只会更新第一个匹配的值,可通过设置options {multi: true}设置匹配多个文档并更新

1
2
3
4
5
6
7
8
# <query> 定义了更新时的筛选条件
# <update> 文档提供了更新内容
# <options> 声明了一些更新操作的参数
db.collection.update(<query>,<update>,<options>)
# 默认删除所有满足条件的文档,可设定参数{justOne:true},只删除满足条件的第一条文档
db.collection.remove(<query>,<options>)
# 删除集合,不但删除集合内的所有文档,且删除集合的索引
db.collection.drop({writeConcern:<doc>})

聚合操作

聚合表达式
  • $<field>.<sub_field>:获取字段信息,sub_field字段可0个或多个
  • $literal: <value>:常量表达式
  • $$<variable>:系统变量表达式
  • $$CURRENT:指示管道中当前操作的文档
聚合管道阶段

筛选管道操作和其他管道操作配合时候时,尽量放到开始阶段,可减少后续管道操作符要操作的文档数,提升效率

  • $project:对输入文档进行再次投影
  • $match:对输入文档进行筛选
  • $limit:筛选出管道内前 N 篇文档
  • $skip:跳过管道内前N篇文档
  • $unwind:展开输入文档中的数组字段
  • $sort:对输入文档进行排序
  • $lookup:对输入文档进行查询操作
  • $group:对输入文档进行分组
  • $out:对管道中的文档输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
db.userInfo.aggregate({$project:{name:"$nickName"}}) # 将原始字段投影成指定名称
db.userInfo.aggregate({$project:{ name:"$nickName",_id:0,age:1}}); # 也可剔除不需要的字段
db.userInfo.aggregate({$match:{nickName:"lisi"}}); # 文档筛选
db.userInfo.aggregate([{$match:{$and:[{age:{$gte:20}},{nickName:{$eq:"lisi"}}]}}, {$project: {_id:0, name: "$nickName", age:1}}]);
db.userInfo.aggregate({$limit:1});
db.userInfo.aggregate({$skip:1});
db.userInfo.aggregate({$unwind:{path:"$tags"}}); # tags是一个数组
db.userInfo.aggregate({$unwind:{path:"$tags",includeArrayIndex:"arrIndex"}}); # 赋值给指定的字段
# 展开时保留空数组,或者不存在数组字段的文档
db.userInfo.aggregate({$unwind:{path: "$tags",includeArrayIndex:"arrIndex",preserveNullAndEmptyArrays:true}});
# 对文档进行排序:1正序,-1倒序
db.userInfo.aggregate({$sort:{age:-1}});
# 单一字段值进行关联查询
db.accountDetail.aggregate({$lookup:{ from: "需要关联的文档",localField: "本地字段",foreignField: "外部文档关联字段",as "作为新的字段,添加到文档中"});

索引

索引默认名称是索引键和索引中每个键的方向即1或-1的连接,使用下划线作为分隔符, 也可以通过指定name来自定义索引名称;

1
2
3
4
5
6
7
8
db.collection.createIndex(<keys>, <options>) # 创建索引
db.members.createIndex({name:1},{name: "index_name"});
db.members.getIndexes(); # 查询集合中已经存在的索引
db.members.createIndex({ name:1,age:-1}); # 复合索引,按name升序排age降序
db.members.createIndex({age:1},{unique:true}); # 唯一性索引
db.sparsedemo.createIndex({name:1},{unique:true ,sparse:true}); # 稀疏索引
# 日期字段或包含日期元素的数组字段,可使用设定生存时间的索引,来自动删除字段值超过生存时间的文档
db.members.createIndex({create_time:1},{expireAfterSeconds:30});

复制集

MongoDB复制集主要意义在于实现服务高可用,类似于Redis哨兵模式,数据写入Primary主节点时将数据复制到另一个Secondary副本节点上,主节点发生故障时自动选举出一个新的替代节点。实现高可用的同时,复制集还有如下作用

  • 数据分发:将数据从一个区域复制到另一个区域,减少另一个区域的读延迟
  • 读写分离:不同类型的压力分别在不同的节点上执行
  • 异地容灾:在数据中心故障时快速切换到异地

典型的复制集由三个三个以上具有投票权的节点组成,其中一个Primary主节点接收写入操作读操作选举时投票,两个或多个Secondary从节点复制主节点上的新数据选举时投票

当一个修改操作到达主节点时,它对数据的操作将被记录下来,这些记录称为oplog,从节点通过从主节点上不断获取新进入主节点的oplog,并在自己的数据上回放,以此保持跟主节点的数据一致。

具有投票权的节点之间两两互相发送心跳,当5次心跳未收到时判断为节点失联,若主节点失联,从节点会发起选举,选出新的主节点,若从节点失联不会产生新的选举,选举基于RAFT一致性算法实现,选举成功的必要条件是大多数投票节点存活复制集中最多可有50个节点,但具有投票权的节点最多7

整个集群必须有大多数节点存活,被选举为主节点的节点必须能够与多数节点建立连接具有较新的oplog,具有较高优先级。复制集节点有以下的选配项:

  • v:是否具有投票权,有则参与投票
  • priority:优先级,优先级越高的节点越优先成为主节点。优先级为0的节点无法成为主节点,默认值为1。
  • hidden:隐藏,复制数据,但对应用不可见。隐藏节点可具有投票权,但优先级必须为0
  • slaveDelay:延迟,复制n秒之前的数据,保持与主节点的时间差
  • buildIndexes:从节点不建立索引

mongod -f /data/db/mongod.conf,默认情况下非主节点不允许读数据,可通过执行rs.secondaryOk()开启读权限

1
2
3
4
5
6
7
8
9
10
11
12
13
systemLog:
destination: file
path: /data/db/mongod.log
logAppend: true
storage:
dbPath: /data/db1
net:
bindIp: 0.0.0.0
port: 28017
replication:
replSetName: rs0
processManagement:
fork: true

分片集群

mongos路由节点提供集群单一入口,转发应用端请求,选择合适的数据节点进行读写,合并多个数据节点的返回无状态,建议mongos节点集群部署以提供高可用性。客户请求应发给mongos而不是分片服务器,当查询包含分片键时,mongos将查询发送到指定分片,否则mongos将查询发送到所有分片,并汇总所有查询结果。

配置节点: 普通的mongod进程, 建议以复制集部署,提供高可用,提供集群元数据存储分片数据分布的数据。主节点故障时配置服务器进入只读模式只读模式下数据段分裂和集群平衡都不可执行整个复制集故障时,分片集群不可用

数据节点:以复制集为单位,横向扩展最大1024分片分片之间数据不重复,所有数据在一起才可以完整工作。

数据段的分裂,当数据段尺寸过大,或包含过多文档时,触发数据段分裂,只有新增、更新文档时才可能自动触发数据段分裂,数据段分裂通过更新元数据来实现

集群的平衡,后台运行的平衡器负责监视和调整集群的平衡,当最大和最小分片之间的数据段数量相差过大时触发 ,集群中添加或移除分片时也会触发

MongoDB分片集群特点:应用全透明,数据自动均衡,动态扩容,无需下线