玩转MongoDB:基础杂萃

作者: 一只老辣鸡 | 来源:发表于2016-07-08 14:50 被阅读648次
    图片来自Google搜索

    0 由于工作需要,最近开始接触MongoDB,对于一个已经习惯了传统SQL的程序员来说,进入NoSQL可以说是需要莫大的勇气的。最大的不适应,是NoSQL和SQL的存储原理与思维方式的不一致。当然,笔者也是NoSQL中的菜鸟,在这里分享一下自己的一些学习总结与心得。

    1、数组

    在MongoDB中,文档(Document)即表示数据库中的一个集合中的一条记录,相当于关系型数据库中的行(row)。在MongoDB中,数组是使用JSON语法表示的,在MongoDB中,也称为BSON格式。数组既可以作为有序对象来操作,也可以作为无序对象来操作。有序对象比如列表等,无序对象比如集合等等。

    {
      "a" : 0,               //这明显不是一个数组
      "b" : [],              //定义一个空的数组 
      "c" : [ "1","2","3"]   //包含3个元素的数组
    }
    
    2、文档嵌套

    在NoSQL中,文档与文档之间是可以随意嵌套的,比如某个字段的值为某个类型的对象。理论上,MongoDB支持无限级的自我嵌套,比如:

    {
      "a": {"b":1,"c":2}
    }
    
    3、文档标识

    如果标记当前这个文档,按照关系型数据库的习惯,则可以说,如果保证一条记录在数据库里是唯一的,在传统SQL中,使用主键ID来标识记录的全局唯一性,而在MongoDB中,则使用ObjectId来确保文档的唯一标识,即_id的默认类型。
    ObjectID是一个12字节的BSON数据类型,格式如下:

    1. 前四个字节表示时间戳
    2. 接下来三个字节是机器标识码
    3. 紧接的两个字节由进程ID组成,即PID
    4. 最后三个字节是随机数

    MongoDB中的每个文档必须有一个名为_id的键,可以是任意类型,默认为ObjectID类型。

    以下是在Shell中关于ObjectId的一些方法

    #生成一个新的ObjectId
    newObjectId = ObjectId()
    
    #返回的id为:ObjectId("5349b4ddd2781d08c09890f3")
    
    #获取文档
    ObjectId("5349b4ddd2781d08c09890f3").getTimestamp()
    
    #返回时间为:ISODate("2016-07-06T21:49:17Z")
    
    #将ObjectId转化为字符串
    new ObjectId().str
    
    #返回结果为:5349b4ddd2781d08c09890f3
    
    3、添加文档

    插入文档使用db.col.insert(document)

    db.Collection_Name.insert({
      "name":"demo"
    });
    
    1. 如果文档不包含_id键,MongoDB会自动创建一个ObjectId类型的_id值
    2. 默认情况下插入操作时,MongoDB只检查传入数据是否包含_id以及数据大小是否超过16MB,所以可以得到更高的性能插入,但同时也可能录入无效数据。
    3. 因为在插入时是不执行任何代码的,所以与传统SQL相比,MongoDB不存在SQL注入风险。
    4、删除文档
    db.Collection_Name.remove();   //清空集合内的所有文档
    db.Collection_Name.remove(    //清空指定文档
      {
         "a":"a"
      }
    );
    
    //当集合内数据过多时,可以考虑下面这个方法
    
    db.drop_collection(Collection_Name);  //直接删除集合
    db.Collection_Name.ensureIndex();      //重建索引
    
    
    5、更新文档
    //update语法定义
    db.Collection_Name.update(query,document,upsert,multi);
    
    //设定原文档为:
    var data = {_id:"xxxx","a":1,"b":2};
    
    //query 是指查询条件,相当于SQL中的where子句,比如:
    db.Collection_Name.update(
      {_id:"xxxx","a":1,"b":2},       //定位条件,对符合_id=xxxx,a=1,b=2的文档进行更新
      {"a":2},                        //将a的值改为2,替换整个文档
      true,                           //若查询不到符合条件的文档,则新增一个文档
      true                            //允许更新多行
    );
    
    
    1. update操作会替换整个匹配的文档。而不是进行某些特定字段的修改。如果需要更新某个特定字段值,则应当使用修改器。
    1. upsert模式是一个布尔值选项,表示是否文档更新时,如果不存在,能够自动创建。
    2. multi模式也是一个布尔值选项,默认情况下只更新匹配到的第一个文档,开启了multi模式后(即设置为true),则会更新所有匹配的文档。
    //更新文档使用到的一些修改器,由$符号定义
    
    //$inc 增加或减少数字的值,键不存在时自动创建
    db.Collection_Name.update(
      {"name" : "翘着二郎腿打代码"},
      {"$inc" : { "lover" : 1 }}               //只将lover字段的值加1
    );
    
    //$set 设置某一项或者多个项目的值
    db.Collection_Name.update(
      {"name" : "翘着二郎腿打代码"},
      {"$set" : {"name" : "打代码" }}
    );
    

    这里列举一些常用的修改器

    $inc      设置自增或者自减
    $set      设置指定键的值
    $unset    $set的反操作,会删除键及键值
    $push     将元素追加到数组末尾,数组不存在则自动创建
    $pushAll  $push的批量操作版本
    $addToSet 与$push一样,会自动过滤重复元素
    $pop      从数组中移除元素,1代表从末尾移除,-1代码从开头移除
    $pull     从数组中移除所有匹配的元素
    $pullAll  $pull的批量操作版本
    $rename   修改制定键的键名
    $bit      对整型键进行位操作
    

    另外,还有一种方式可以实现文档的更新:

    //使用findAndModify()更新文档
    db.Collection_Name.findAndModify(
      {
          'query' : {"name" : "翘着二郎腿打代码" },
          'update' : {"$set" : { "favour" : 100 } },
          'new' : true
      }
    );
    
    /**
    其中的参数如下:
    
    query   :  查询条件,用来定位到匹配的文档
    sort    :  如果匹配到多个文档,指定一个排序方式,-1降序,1升序
    remove  :  是否删除匹配的文档
    new     :  是否返回更新后的文档
    update  :  更新操作
    upsert  :  是否自动创建,如果匹配不到文档
    
    **/
    

    save()

    //文档不存在时,执行insert操作,存在是执行update操作。
    db.demo.save(document);
    
    6、查询

    我们先来举一些简单的例子:

    //select * from demo;
    db.demo.find();
    
    //select * from demo where a = 1;
    db.demo.find({"a":1});
    
    //select a,b from demo where a = 1;
    db.demo.find({"a":1},{a:1,b:1});
    
    //select * from demo where a = 1 order by name asc;
    db.demo.find({"a":1}).sort({"name":1});
    
    //select * from demo where a > 1;
    db.demo.find({"a":{$gt:1}});
    
    //select * from demo where a like 'eee';
    db.demo.find({"name":"/^eee/"});
    
    //select * from demo limit 10 skip 20;
    db.demo.find().limit(10).skip(20);
    
    

    当然还有很多其他的语法形式,这里不再一一列举。下面列举一些常见的查询条件操作符

    $lt    #小于
    $lte   #小于等于
    $gt    #大于
    $gte   #大于等于
    $all   #完全匹配
    $mod   #取模
    $ne    #不等于
    $in    #在...内
    $nin   #不在...内
    $nor   #既不...也不...
    $or    #或
    $size  #匹配数组长度
    $type  #匹配数据类型
    

    slice()函数用于数组的查询

    db.demo.find({},{favours:{'$slice':1}});     //仅返回数组中的前1项
    db.demo.find({},{favours:{'$slice':-1}});    //仅返回数组中的最后一项
    db.demo.find({},{favours:{'$slice':[1,2]}}); //跳过前1项,返回接下来的10项
    db.demo.find({},{favours:{'$slice':[-1,1]}});//跳过最后一项,返回接下来的1项
    
    7、游标

    MongoDB中的游标已经在各个版本的驱动程序中封装好了,不需要像传统SQL那样使用PL/SQL结构化编程来声明游标,在Shell中,游标的使用方式与Java中的迭代器十分相似。

    var cursor = db.demo.find();  //声明游标
    while(cursor.hasNext()){      //遍历集合
      var element = cursor.next();
    }
    
    8、$WHERE

    $where操作符也是用来定位查询的,这个SQL中的where非常类似,前面我们说过,匹配文档的时候可以使用各种查询条件操作符来实现,但是为什么还要有这个操作符呢?因为有些查询是无法通过之前讲过 的那些查询操作符来实现的。值得注意的是,$where操作符的性能低,没有使用也无法使用索引机制。

    //传统SQL
    select * from demo where a > 1;
    
    //使用查询操作符实现
    db.demo.find({a:{"$gt":1}});
    
    //使用$where实现
    db.demo.find({"$where":"this.a > 1"});
    db.demo.find("this.a > 1");
    db.demo.find(function(){
      return this.a > 1;
    });
    
    9、 排序&分页

    MongoDB中提供了相关的方法进行排序和分页,主要有limit(),skip()sort()

    //每页10条记录,略过前面10条记录,按a降序排序
    db.demo.find().limit(10).skip(10).sort({a:-1});
    
    10、索引

    MongoDB的索引机制与传统SQL的索引基本上是一样的。

    //创建索引
    db.demo.ensureIndex({'a':1});
    
    //创建子文档索引
    db.demo.ensureIndex({'a.b':-1});
    
    //创建复合索引
    db.demo.ensureIndex({
       "a":1,"b":-1
    });
    
    //在MongoDB中,1表示升序,-1表示降序
    
    //重新索引,一般是修改索引后重新索引操作
    db.demo.reIndex();
    
    //删除索引
    db.demo.dropIndexes();
    
    11、聚合

    count()

    //select count(a) from demo where a = 1;
    db.demo.count({"a":1});
    

    distinct()

    db.demo.distinct("zip-code",{a:1});
    

    group(key,cond,reduce,initial)
    其中,
    key :分组依据
    cond: 查询条件
    reduce:聚合操作
    initial : 指定聚合计数器的初始对象

    //sql表示
    select a,b,sum(c) from demo where a = 1 group by a,b;
    
    //MongoDB表示
    db.demo.gourp({
      "key":{
         "a": true,
         "b": true
      },
      "cond":{
         "a":1
      }
    });
    

    12、归纳一下MongoDB中的一些Tips

    1、MongoDB与传统SQL的显著区别

    SQL MongoDB
    表(Table) 集合(Collection)
    行(row) 文档(document)

    2、集合不能以system.开头, 这是因为MongoDB中的系统集合保持的前缀。比如

    db.system.update(document);  //错误
    

    3、 ObjectId类型是_id的默认类型,也可以自己指定其数据类型。MongoDB的初衷是设计成一个分布式的数据库,所以不会自动实现_id的自增。插入文档时,如果没有指定_id的值,则系统自动创建一个ObjectId类型的值,一般在客户端的驱动程序中完成。


    4、插入文档时,MongoDB会解析BSON数据,BSON数据格式与JSON基本一致,在MongoDB中称为BSON。插入时会检查是否包含_id以及检查文档数据是否超过16MB,其余全部不作检查,从而实现其高效率性。


    5、MongoDB在插入数据时,不会执行插入数据的代码,而是将BSON数据直接写入,不作任何的数据验证,所以不存在类似于SQL中的注入风险。


    说明

    本篇文章也是笔者自己在学习过后总结出来的。也是针对习惯于传统SQL的简友们写的,传统的SQL数据库与NoSQL个人感觉差别还算是挺大的。刚开始接触NoSQL的时候最大的不适应就是在MongoDB中是不需要设计表和表结构的,全是基于JSON的数据操作,所以其逻辑原理都在程序代码中实现,而MongoDB本身只负责分布式的数据存储。


    文章更新日志
    2016-07-08 文章初稿
    

    相关文章

      网友评论

      本文标题:玩转MongoDB:基础杂萃

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