简介
- NoSQL : 全名为Not Only SQL, 指的是非关系型的数据库
- 随着访问量上升, 网站的数据库性能出现了问题, 于是nosql被设计出来
- 优点 / 缺点
- 优点:
- 高可扩展性
- 分布式计算
- 低成本
- 架构的灵活性, 半结构化数据
- 没有复杂的数据
- 缺点:
- 没有标准化
- 有限的查询功能
- 最终一致是不直观的程序
- 优点:
Mongo DB
基本操作
一) 简介:
- MongoDB是一个基于分布式文件存储的NoSQL数据库
- 由C++语言编写, 运行稳定, 性能高
- 旨在为WEB应用提供可扩展的高性能数据存储解决方案
- Mongo DB特点:
- 模式自由: 可以把不同结构的文件存储在同一个数据库里
- 面向集合的存储: 适合存储JSON风格文件的形式
- 完整的索引支持: 对任何属性可索引
- 复制和高可用性: 支持服务器之间的数据复制, 支持主-从模式及服务器之间的相互复制. 复制的只要目的是提供用于及自动故障转移
- 自动分片: 支持云级别的伸缩性: 自动分片功能支持水平的数据库集群, 可动态添加额外的机器
- 丰富的查询: 支持丰富的查询表达方式, 查询指令使用JSON形式的标记, 可轻易查询文档中的内嵌的对象及数组
- 快速就地更新: 查询优化器会分析查询表达式, 并生成一个高效的查询计划
- 高效的传统存储方式: 支持二进制数据机大型对象(照片)
二) 基本操作:
- 三元素 : 数据库, 集合, 文档
- 集合就是关系数据库中的表
- 文档对应着关系数据库中的行
- 文档 : 就是一个对象, 由键值对构成, 是json的扩展Bson形式
- 集合 : 类似于关系数据库中的表, 储存多个文档, 结构不固定, 如可以存储文档在集合中
- 数据库 : 是一个集合的物理容器, 一个数据库中可以包含多个文档
- 一个服务器通常有多个数据库
- 三元素 : 数据库, 集合, 文档
三) 环境安装(Ubuntu)
- 1> 下载mongodb的版本(偶数稳定版, 奇数开发版)
- 2> 解压:
tar -zxvf mongodb-linux-x86_64-ubuntu1604-3.4.0.tgz
- 3> 移动到/usr/local目录下面:
sudo mv - rmongodb-linux-x86_64-ubuntu1604-3.4.0/ /usr/local/mongodb
- 4> 将可执行文件添加到PATH路径中:
export PATH=/usr/local/mongo/bin:$PATH
四) 服务端maogod
- 配置文件在/etc/mongd.conf
- 默认端口27017
- 启动:
sudo service mongod start
- 停止:
sudo service mongod stop
- 重启:
sudo service mongod restart
五) 集合操作
- 1> 集合创建
- 语法:
db.createCollection(name, options)
- name : 是要创建的集合名称
- options : 是一个文档, 用于指定集合的配置
- 选项参数是可选的, 所以只要指定的集合名称
- 例子 :
db.createCollection("stu")
-
db.createCollection("stu", { capped : true, size : 10})
- capped : 默认值为false表示不设置上限, 值为true表示设置上限
- size : 当capped值为true的时候, 需要指定此参数, 表示上限大小, 当文档达到上限时, 会将之前的数据覆盖, 单位为字节
- 语法:
- 2> 查看当前数据库的集合
- 语法 :
show collections
- 语法 :
- 3> 删除
- 语法 :
db.集合名称.drop()
- 语法 :
- 1> 集合创建
六) 数据类型
MongoDB中差用的集中数据类型:
- ObjectID : 文档ID
- String : 字符串, 最常用, 必须是有效的UTF-8
- Boolean : 存储一个布尔值, true/false
- Integer : 整数可以是32位或64位, 这取决于服务器
- Double : 存储浮点值
- Arrays : 数组或列表, 多个值存储到一个键
- Object : 用于嵌入式文档, 即一个职为一个文档
- Null : 存储Null值
- Date : 存储当前日期或时间的UNIX时间格式
object id :
- 每个文档都有一个属性为: _id, 保证每个文档的唯一性
- 可以自己去设置_id插入文档
- 如果没有提供, 那么MongoDB为每个文档提供了一个独特的_id, 类型为objectID
- objectID是一个12字节的十六进制数
- 前4个字节为当前时间戳
- 接下来3个字节的机器ID
- 接下来的2个字节中MongoDB的服务进程id
- 最后3个字节是简单的增量值
七) 数据操作
1> 插入
- 语法 :
db.集合名称.insert(document)
- 插入文档时, 如果不指定_id参数, MongoDB会为文档分配一个唯一的ObjectId
db.stu.insert({name:'jack', gender:1})
s1={_id:'20170202',name:'hr'} s1.gender=0 db.stu.insert(s1)
- 语法 :
2> 简单查询
- 语法 :
db.集合名称.find()
- 语法 :
3> 更新
- 语法 :
db.集合名称.update( <query>, <update>, {mulit: <boolean>} )
- 参数query : 查询条件, 类似于sql语句update中where部分
- 参数update : 更新操作符, 类似于sql语句update中set部分
- 参数mulit : 可选, 默认是false, 表示只更新找到的第一条记录, 值为true表示把满足条件的文档全部更新
- 例子 :
- 全文档更新(之前的数据全部删除):
db.stu.update({name : 'hr'}, {name : 'erik'})
- 指定属性更新, 通过操作符 $set
db.stu.insert({name: 'jack', gender:0}) db.stu.update({name: 'tom'}, {$set:{name: 'lucky'}})
- 修改多条匹配到的数据:
db.stu.update({}, {$set:{gender:0}}, {multi:true})
- 全文档更新(之前的数据全部删除):
- 语法 :
4> 保存
- 语法 :
db.集合名称.save(document)
- 如果文档_id已经存在则修改, 如果文档_id不存在则添加
- 例子 :
db.stusave({_id:'20170202', 'name':'leo', gender:1})
db.stusave({_id:'20170202', 'name':'leo'})
- 语法 :
5> 删除
- 语法 :
db.集合名称.remove( <query>, { justOne: <boolean> } )
- 参数query : 可选, 删除的文档的条件
- 参数justOne : 可选, 如果设为true或1, 则只删除一条, 默认false, 表示删除多条
- 例子 :
- 只删除匹配到的第一条:
db.stu.remove({gender:0}, {justOne:true})
- 全部删除 :
db.stu.remove({})
- 只删除匹配到的第一条:
- 6> 关于size的示例
- 当超过size的时候, 会覆盖最老的数据
- 语法 :
七) 数据查询
基本查询
- 方法find() : 查询
db.集合名称.find({条件文档})
- 方法findOne() : 查询, 只返回第一个
db.集合名称.findOne({条件文档})
- 方法pretty() : 将结果格式化
db.集合名称.find({条件文档}).pretty()
- 方法find() : 查询
比较运算符
- 小于 : $lt
- 小于或等于 : $lte
- 大于 : $gt
- 大于或等于 : $gte
- 不等于 : $ne
- 例子 :
- 查询name等于old的学生 :
db.stu.find({name: 'old'})
- 查询年龄大于或等于18的学生 :
db.stu.find({age: {$gte:18}})
- 查询name等于old的学生 :
逻辑运算符
- 逻辑与 : 默认是逻辑与的关系
- 逻辑或 : 使用$for
- 查询年龄大于18, 或性别为0的学生 : “ db.stu.find({$for:[{age:{$gt:18}}, {gender:1}]})
- or 和 and 一起使用:
- 查询年龄大于18, 或性别为0的学生, 并且学生的姓名为old : “ db.stu.find({$for:[{age:{$gt:18}}, {gender:1}], name:’old’})
范围运算符
- 使用$in, $nin 判断是否在某个范围内
- 查询年龄为18, 28的学生 :
db.stu.find({age:{$in:[18,28]}})
支持正则表达式
- 使用
//
或者$regex
编写正则表达式 - 例子 : 查询姓黄的学生
db.stu.find({name:/^黄/}) db.stu.find({name:{name: {$regex:'/^黄'}}})
- 使用
自定义查询
- 使用$where后面写一个函数, 返回满足条件的数据
- 查询年龄大于30的学生:
db.stu.find({$where:function(){return this.age>20}})
八) 高级查询
limit() : 用于读取指定数量的文档
- 语法:
db.集合名称.find().limit(NUMBER)
- 参数NUMBER: 表示要获取文档的条数
- 如果没有制定参数则显示集合中的所有文档
- 例子: 查询2条学生信息
db.stu.find().limit(2)
- 语法:
skip() : 用于跳过指定数量的文档
- 语法 :
db.集合名称.find().skip(NUMBER)
- 参数NUMBER: 表示跳过的记录条数, 默认值为0
- 例子 : 查询从第三条开始的学生信息
db.stu.find().skip(2)
- 语法 :
投影:
- 在查询的返回结果中, 只选择必要的字段, 而不是选择一个文档的整个字段
- 语法 :
db.集合名称.find({}, {字段名称:1, ...})
-> 参数为字段与值, 值为1表示显示, 为0表示不显示 - 特殊: 对于_id列默认是显示的, 如果不显示需要明确设置为0
排序:
- 方法sort(): 用于对结果集进行排序
- 语法 :
db.集合名称.find().sort({字段:1,...})
-> 参数1为升序, -1为降序 - 例子 :
db.stu.find().sort({gender:-1,age:1})
统计个数:
- 方法count(): 用于统计结果集中文档条数
- 语法:
db.集合名称.find({条件}).count()
- 例子 :
- 统计男生人数:
db.stu.find({gender:1}).count()
- 统计年龄大于20的男生人数: “db.stu.find({age{$gt:20}, gender:1})
- 统计男生人数:
消除重复:
- 方法distinct() : 对数据进行去重
- 语法 :
db.集合名称.distinct('去重字段', {条件})
- 例子 : 查找年龄大于18的性别(去重) ->
db.stu.distinct('gender', {age:{$gt;20}})
高级操作
聚合, 主从复制, 分片, 备份与恢复, MR
一) 聚合(aggregate)
聚合主要用于计算数据, 类似sql中的sum(), avg()
语法:
db.集合名称.aggregate([{管道: {表达式}}])
管道
- 管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的输入 :
ps ajx | grep mongo
- 在mongodb中, 管道具有同样的作用, 文档处理完毕后, 通过管道进行下一次处理
- 常用管道
- $group : 将集合中的文档分组, 可用于统计结果
- $match : 过滤数据, 只输出符合条件的文档
- $project : 修改输入文档的结构, 如重命名, 增加, 删除字段, 创建计算结果
- $sort : 将输入文档排序后输出
- $limit : 限制聚合管道返回的文档数
- $skip : 跳过制定数量的文档, 并返回余下的文档
- $unwind : 将数组类型的字段进行拆分
- 管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的输入 :
表达式
- 处理输入文档并输出
- 语法 :
表达式:'$列名'
- 常用表达式 :
- $sum : 计算综合, $sum:1同count表示技术
- $avg : 计算平均值
- $max : 获取最大值
- $push : 在结果文档中插入值到一个数组中
- $first : 根据资源文档的排序获取第一个文档数据
- $last : 根据资源文档的排序获取最后一个文档数据
- $group:
1> group
- 将集合中的文档分组, 可用于统计结果
- _id表示分组的依据, 使用某个字段的格式为’$字段’
- 例子 : 统计男生, 女生的总人数
db.stu.aggregate({ {$group: { _id : '$gender', counter : {$sum:1} } } })
2> Group by null
- 将集合中所有文档分为一组
- 例子 : 求学生总人数, 平均年龄
db.stu.aggregate({ {$group: { _id : null, counter : {$sum:1} } } })
3> 透视数据($push)
- 例子 : 统计学生性别及学生姓名
db.stu.aggregate({ {$group: { _id : '$gender', name : {$push:'$name'} } } })
- 使用$$ROOT可以将文档内容加入到结果集的数组中, 代码如下:
db.stu.aggregate({ {$group: { _id : '$gender', name : {$push:'$$ROOT'} } } })
- 例子 : 统计学生性别及学生姓名
- $match:
- 用于过滤数据, 只输出符合条件的文档
- 使用MongoDB的标准查询操作
- 例子:
- 查询年龄大于20的学生
db.stu.aggregate([ {$match:{age:{$gt:20}}} ])
- 查询年龄大于20的男生, 女生人数
db.stu.aggregate([ {$match:{age:{$gt:20}}}, {$group:{_id:'gender', counter:{$sum:1}}} ])
- 查询年龄大于20的学生
- $project:
- 修改输入文档的结构, 如重命名, 增加, 删除字段, 创建计算结果
- 例子:
- 查询学生的年龄, 姓名
db.stu.aggregate([ {$project:{_id:0, name:1, age:1}} ])
- 查询男生, 女生人数, 输出人数
db.stu.aggregate([ {$group:{_id:'$gender', counter:{$sum:1}}}, {$project:{_id:0, counter:1}} ])
- 查询学生的年龄, 姓名
- $sort
- 将输入文档排序后输出
- 例子:
- 查询学生信息, 按年龄升序
db.stu.aggregate([ {$sort:{age:1}} ])
- 查询男生, 女生人数, 按人数降序
db.stu.aggregate([ {$group:{_id:'$gender', counter:{$sum:1}}}, {$sort:{counter:-1}} ])
- 查询学生信息, 按年龄升序
- $limit
- 限制聚合管道返回的文档数
- 例子: 查询2条学生信息 ->
db.stu.aggregate([{$limit:2}])
- $skip
- 跳过制定数量的文档, 并返回余下的文档
- 例子:
- 查询从第三条开始的学生信息 ->
db.stu.aggregate([{$skip:2}])
- 查询男生, 女生人数, 按人数升序, 取第二条数据(先写skip, 后limit)
db.stu.aggregate([ {$group:{_id:'$gender', counter:{$sum:1}}}, {$sort:{counter:-1}}, {$skip:1} {$limit:1} ])
- 查询从第三条开始的学生信息 ->
- $unwind
将文档中的某一个数组类型字段拆分成多条, 每条包含数组中的一个值
语法1
- 对某字段值进行拆分 :
db.集合名称.aggregate([{$unwind:'$字段名称'}])
- 构造数据 :
db.t2.insert({_id:1, item:'t-shirt', size:['S', 'M', 'L']})
- 查询 :
db.t2.aggregate([{$unwind:'$size'}])
- 对某字段值进行拆分 :
语法2
对某字段值进行拆分
处理空数组, 非数组, 无字段, null情况
db.inventory.aggregate([ {$unwind:{ path:'$字段名称', preserveNullAndEmptyArrays:<boolean>#防止数据丢失 } ])
构造数据
db.t3.insert([ {_id:1, item:'a', size:['S', 'M', 'L']}, {_id:2, item:'b', size:[]}, {_id:3, item:'c', size:'M'}, {_id:4, item:'d'}, {_id:5, item:'t', size:null} } ])
使用语法1查询 :
db.t3.aggregate([{$unwind:'$size'}])
- 查看查询结果, 发现对于空数组, 无字段, null的文档, 都被丢弃了
- 如何防止数据不丢失 :
db.t3.aggregate([ {$unwind:{path:'$size', preserveNullAndEmptyArrays:true}} ])
二) 索引
1> 创建大量的数据
- 向集合中插入10万条文档
for(i=0;i<100000;i++){ db.t1.insert({name: 'test'+i,age:i}) }
2> 数据查找性能分析
- 查找姓名为’test10000’的文档 :
db.t1.find({name:'test10000'})
- 使用explain()命令进行查询性能分析 :
db.t1.find({name:'test10000'}).explain('executionStats')
- 其中的executionStats下的executionTimeMillis表示整体查询时间, 单位是毫秒
- 查找姓名为’test10000’的文档 :
3> 建立索引
- 创建索引 : 1->表示升序, -1->表示降序
# db.集合.ensureIndex({属性:1}) db.t1.ensureIndex({name:1})
- 创建索引 : 1->表示升序, -1->表示降序
4> 对索引属性查询
- 执行上面的同样的查询, 并进行查询性能分析 :
db.t1.find({name:'test10000'}).explain('excutionStats')
- 执行上面的同样的查询, 并进行查询性能分析 :
索引的命令
- 建立唯一索引, 实现唯一约束的功能 : “db.t1.ensureIndex({‘name’:1}, {‘unique’:true})
- 联合索引, 对多个属性建立一个索引, 按照find()出现的顺序 :
db.t1.ensureIndex({name:1,age:1})
- 查看文档所有索引 :
db.t1.getIndexes()
- 删除索引 :
db.t1.dropIndexes('索引名称')
三) 安全性
- 超级管理员
- 为了更安全的访问mongodb, 需要访问者提供用户名和密码, 于是需要在mongodb中创建用户
- 采用了角色-用户-数据库的安全管理方式
- 常用系统角色如下:
- root : 只在admin数据库中可用, 超级账号, 超级权限
- Read : 允许用户读取制定数据库
- readWrite : 允许用户读写指定数据库
- 创建超级管理用户
use admin db.create({ user:'admin', pwd:'123', roles:[{role:'root', db:'admin'}] })
- 启用安全认证
- 修改配置文件 :
sudo vi /etc/mongod.conf
- 启用身份验证(注意: keys和values之间一定要加空格, 否则解析会报错)
security: authorization: enabled
- 重启服务 :
sudo service mongod restart
- 终端连接 :
mongo -u admin -p 123 --authenticationDatabase admin
- 切换数据库, 执行命令查看结果
- 修改用户 : 可以修改pwd, roles属性 ->
db.updateUser('t1':[pwd:'456'])
- 修改配置文件 :
- 超级管理员
四) 复制(副本集)
什么是复制
- 复制提供了数据的冗余备份, 并在多个服务器上存储数据副本, 提高了数据的可用性, 并可以保证数据的安全性
- 复制还允许从硬件故障和服务中断中恢复数据
为什么要复制
- 数据备份
- 数据灾难恢复
- 读写分离
- 高(24*7)数据可用性
- 无宕(dang)机维修
- 副本集对应用程序是透明
复制的工作原理
- 复制至少需要两个节点A, B…
- A是主节点, 负责处理客户端请求
- 其余的都是从节点, 负责复制主节点上的数据
- 节点常见的搭配方式为: 一主一从, 一主多从
- 主节点记录在其上的所有操作, 从节点定期轮询主节点获取这些操作, 然后对自己的数据副本执行这些操作, 从而保证从节点的数据与主节点一致
- 主节点与从节点进行数据交互保障数据的一致性
复制的特点
- N个节点在集群
- 任何节点可作为主节点
- 所有写入操作都在主节点上
- 自动故障转移
- 自动恢复
设置复制节点
- 接下来的操作需要打开多个终端窗口, 而且可能会连接多台ubuntu主机, 会显得有些乱, 建议在xshell中实现
- 1> 创建数据库目录t1, t2
mkdir t1 mkdir t2
- 2> 使用如下格式启动mongod, 注意replSet的名称是一致的
mongod --bind_ip 192.168.196.128 --port 27017 --dbpath ~/Desktop/t1 --replSet rs0 mongod --bind_ip 192.168.196.128 --port 27018 --dbpath ~/Desktop/t1 --replSet rs0
- 3> 连接主服务器, 此处设置192.168.196.128:27017为主服务器 :
mongo --host 192.168.196.128 --port 27017
- 4> 初始化 :
rs.initiate()
- 5> 查看当前状态 :
rs.status()
- 6> 添加复本集 :
rs.add('192.168.196.128:27018')
- 7> 连接第二个mongo服务 :
- 8> 向主服务器中插入数据
use test1 for(i=0;i<10;i++){db.t1.insert({_id:i})}
- 9> 在从服务器中查询(如果在从服务器上进行读操作, 需要设置rs.slaveOk())
rs.slaveOk() db.t1.find()
其他说明
- 删除从节点 :
rs.remove('192.168.196.128:27018')
- 关闭主服务器后, 再重新启动, 会发现原来的主服务器变为了从服务器, 新启动的服务器(原来的从服务器)变为了从服务器(主从自动切换)
- 删除从节点 :
五) 备份与恢复
备份
- 语法:
mongodump -h - dbhost -d dbname -o dbdirectory
- h : 服务器地址, 也可以指定端口号
- d : 需要备份的数据库名称
- o : 备份的数据存放位置, 此目录中存放着备份出来的数据
- 例子 :
sudo mkdir test1bak sudo mongodump -h 192.168.196.128:27017 -d test1 -o ~/Desktop/test1bak
- 语法:
恢复
- 语法:
mongorestore -h dbhost -d dbname --dir dbdirectory
- h : 服务器地址
- d : 需要恢复的数据库实例
-
--dir
: 备份的数据所在位置 - 例子 :
mongorestore -h 192.168.196.128:27017 -d test2 --dir ~/Desktop/test1bak/test1
- 语法: