美文网首页我爱编程
NoSQL: MongoDB (文档存储)

NoSQL: MongoDB (文档存储)

作者: 奋斗的老王 | 来源:发表于2018-03-14 12:17 被阅读395次

    简介

    • NoSQL : 全名为Not Only SQL, 指的是非关系型的数据库
    • 随着访问量上升, 网站的数据库性能出现了问题, 于是nosql被设计出来
    • 优点 / 缺点
      • 优点:
        • 高可扩展性
        • 分布式计算
        • 低成本
        • 架构的灵活性, 半结构化数据
        • 没有复杂的数据
      • 缺点:
        • 没有标准化
        • 有限的查询功能
        • 最终一致是不直观的程序

    Mongo DB

    基本操作
    • 一) 简介:

      • MongoDB是一个基于分布式文件存储的NoSQL数据库
      • 由C++语言编写, 运行稳定, 性能高
      • 旨在为WEB应用提供可扩展的高性能数据存储解决方案
      • Mongo DB特点:
          1. 模式自由: 可以把不同结构的文件存储在同一个数据库里
          1. 面向集合的存储: 适合存储JSON风格文件的形式
          1. 完整的索引支持: 对任何属性可索引
          1. 复制和高可用性: 支持服务器之间的数据复制, 支持主-从模式及服务器之间的相互复制. 复制的只要目的是提供用于及自动故障转移
          1. 自动分片: 支持云级别的伸缩性: 自动分片功能支持水平的数据库集群, 可动态添加额外的机器
          1. 丰富的查询: 支持丰富的查询表达方式, 查询指令使用JSON形式的标记, 可轻易查询文档中的内嵌的对象及数组
          1. 快速就地更新: 查询优化器会分析查询表达式, 并生成一个高效的查询计划
          1. 高效的传统存储方式: 支持二进制数据机大型对象(照片)
    • 二) 基本操作:

      • 三元素 : 数据库, 集合, 文档
        • 集合就是关系数据库中的表
        • 文档对应着关系数据库中的行
      • 文档 : 就是一个对象, 由键值对构成, 是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()
    • 六) 数据类型

      • 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()
      • 比较运算符

        • 小于 : $lt
        • 小于或等于 : $lte
        • 大于 : $gt
        • 大于或等于 : $gte
        • 不等于 : $ne
        • 例子 :
          • 查询name等于old的学生 : db.stu.find({name: 'old'})
          • 查询年龄大于或等于18的学生 : db.stu.find({age: {$gte:18}})
      • 逻辑运算符

        • 逻辑与 : 默认是逻辑与的关系
        • 逻辑或 : 使用$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 : 将数组类型的字段进行拆分
      • 表达式

        • 处理输入文档并输出
        • 语法 : 表达式:'$列名'
        • 常用表达式 :
          • $sum : 计算综合, $sum:1同count表示技术
          • $avg : 计算平均值
          • $max : 获取最大值
          • $push : 在结果文档中插入值到一个数组中
          • $first : 根据资源文档的排序获取第一个文档数据
          • $last : 根据资源文档的排序获取最后一个文档数据
      1. $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'}
              }
            }
          })
          
      1. $match:
      • 用于过滤数据, 只输出符合条件的文档
      • 使用MongoDB的标准查询操作
      • 例子:
        • 查询年龄大于20的学生
            db.stu.aggregate([
              {$match:{age:{$gt:20}}}
            ])
          
        • 查询年龄大于20的男生, 女生人数
            db.stu.aggregate([
              {$match:{age:{$gt:20}}},
              {$group:{_id:'gender', counter:{$sum:1}}}
            ])
          
      1. $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}}
            ])
          
      1. $sort
      • 将输入文档排序后输出
      • 例子:
        • 查询学生信息, 按年龄升序
            db.stu.aggregate([
              {$sort:{age:1}}
            ])
          
        • 查询男生, 女生人数, 按人数降序
            db.stu.aggregate([
              {$group:{_id:'$gender', counter:{$sum:1}}},
              {$sort:{counter:-1}}
            ])
          
      1. $limit
      • 限制聚合管道返回的文档数
      • 例子: 查询2条学生信息 -> db.stu.aggregate([{$limit:2}])
      1. $skip
      • 跳过制定数量的文档, 并返回余下的文档
      • 例子:
        • 查询从第三条开始的学生信息 -> db.stu.aggregate([{$skip:2}])
        • 查询男生, 女生人数, 按人数升序, 取第二条数据(先写skip, 后limit)
            db.stu.aggregate([
              {$group:{_id:'$gender', counter:{$sum:1}}},
              {$sort:{counter:-1}},
              {$skip:1}
              {$limit:1}
            ])
          
      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表示整体查询时间, 单位是毫秒
      • 3> 建立索引

        • 创建索引 : 1->表示升序, -1->表示降序
            # db.集合.ensureIndex({属性:1})
            db.t1.ensureIndex({name: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
            

    相关文章

      网友评论

        本文标题:NoSQL: MongoDB (文档存储)

        本文链接:https://www.haomeiwen.com/subject/yyqzfftx.html