美文网首页我爱编程
2、MongoDB基础知识(1)(MongoDB笔记)

2、MongoDB基础知识(1)(MongoDB笔记)

作者: yjaal | 来源:发表于2017-05-10 22:45 被阅读153次

    这里是阅读了《MongoDB权威指南》后做的相关笔记。

    一、文档

    文档是MongoDB的核心概念。文档就是键值对的一个有序集合。在JS中,文档被表示为对象:

    {"greeting" : "Hello, world!"}
    或
    {"greeting" : "Hello, world!", "foo" : 3}
    

    文档的键是字符串。除了少数例外情况,键可以使用任意UTF-8字符。

    • 键不能含有\0(空字符)。这个字符用于表示键的结尾。
    • .$具有特殊意义,只能在特定环境下使用。
    • MongoDB不但区分类型,而且区分大小写。下面两个文旦是不同的:
    {"foo" : 3}
    {"foo" : "3"}
    

    下面两个文档也是不同的:

    {"foo" : 3}
    {"Foo" : 3}
    
    • 文档不能有重复的键。而且文档中的键值对是有序的。下面两个文档是不同的:
    {"x" : 1, "y" : 2}
    {"y" : 2, "x" : 1}
    

    二、集合

    集合就是一组文档。

    2.1 动态模式

    集合是动态的,也就是说,一个集合里的文档可以是各式各样的。如下面的文档可以同时存在于同一个集合中。

    {"greeting" : "Hello, world!"}
    {"foo" : 5}
    

    2.2 命名

    集合使用名称进行标识。集合名可以是满足下列条件的任意UTF-8字符串。

    • 集合名不能是空字符串("")。
    • 集合名不能包含\0字符串(空字符),这个字符表示集合名的结束。
    • 集合名不能以"system."开头,这是为系统集合保留的前缀。如,system.users集合保存着数据库的用户信息,而system.namespaces集合保存着所有数据库集合的信息。
    • 用户创建的集合不能在集合名中包含保留字符'$'。因为某些系统生成的集合中包含它。

    子集合
    组织集合的一种管理是使用"."分隔不同命名空间的子集合。如,一个具有博客功能的应用可能包含两个集合,分别是blog.posts、blog.authors。这是为了使组织结构更清晰,这里的blog集合跟其子集合没有任何关系。

    三、 数据库

    MongoDB中,多个文档年组成集合,而多个集合可以组成数据库。数据库通过名称来标识,这点与集合类似。数据库名可以是满足以下条件的任意UTF-8字符串。

    • 不能是空字符串("")
    • 不得含有/、\、.、"、*、<、>、:、|、?、$(一个空格)、\0(空字符)。基本上,只能使用ASCII中的字母和数字。
    • 数据库名区分大小写,即便是在不区分大小写的文件系统中也是如此。简单起见,数据库名应全部小写。
    • 数据库名最多64字节。

    注意:数据库最终会变成文件系统里的文件,而数据库名就是相应的文件名。另外有一些数据库名是保留的,可以直接访问这些有特殊语义的数据库。

    • admin
      从身份验证的角度来讲,这是"root"数据库。如果将一个用户添加到admin数据库,这个用户将自动获得所有数据库的权限。再者,一些特定的服务器端命令也只能从admin数据库运行,如列出所有数据库或关闭服务器。

    • local
      这个数据库永远都不可以复制,且一台服务器上的所有本地集合都可以存储在这个数据库中。

    • config
      MongoDB用于分片设置时,分片信息会存储在config数据库中。

    四、MongoDB简介

    4.1 shell 中的基本操作

    4.1.1 数据库查看

    db //表示查看当前使用的是哪个数据库
    show dbs //查看所有的数据库
    use test //更换数据库为test
    

    4.1.2 创建

    1
    说明:这里创建一个名为post的局部变量,这是一个JS对象,用于表示文档。注意,换行的时候不能直接使用回车,应先打//对回车进行转义。这个对象是一个有效的MongoDB文档,使用insert方法可以将其保存到blog集合中。
    2
    说明:如上,将文档保存到集合中之后,可以使用find()方法查看。同时,可以看到曾输入的完整文档,还有一个额外添加的键"_id"。其实这就和主键类似。

    4.1.3 读取

    这里可以使用方法findfindOne查询集合里的文档。若只想查看一个文档,可用findOne

    3
    这两个方法可以接受一个查询文档作为限定条件。这样就可以查询符合一定条件的文档。使用find时,shell会自动显示最多二十个匹配的文档,也可以获得更多文档。

    4.1.4 更新

    使用update修改博客文章。其接受(至少)两个参数:第一个是限定条件(用于匹配待更新的文档),第二个是新的文档。现在如果我们要为先前写的文章增加评论功能,就需要增加一个新的键,用于保存评论数组。

    4
    说明:这里先修改post变量,增加了"comments"键。然后执行update操作,其中第一个参数表示限定条件,即匹配标题为"My Blog Post"的文章。

    4.1.5 删除

    使用remove方法可将文档从数据库中永久删除。如果没有使用任何参数,它会将集合内的所有文档全部删除。接受一个作为限定条件的文档作为参数。

    5

    五、数据类型

    5.1 基本数据类型

    在概念上,MongoDB的文档与JS中的对象相近,因而可认为它类似于JSONJSON是一种简单的数据表示方式:其规范仅用一段文字就能描述清楚,且仅包含六种数据类型。这其中只有null、布尔、数字、字符串、数组和对象这几种数据类型,所以JSON的表达能力有一定的局限。如JSON没有日期类型。只有一种数字类型,无法区分浮点数和整数,更别说区分32位和64位数字了。基于此原因,MongoDB在保留JSON基本键值对的基础上,添加了其他一些数据类型。

    • null

    null用于表示空值或者不存在的字段:

    {"x" : null}
    
    • 布尔值
      布尔类型有两个值truefalse
    {"x" : true}
    
    • 数值
      shell默认使用64位浮点数值。
    {"x" : 3.14}
    {"x" : 3}
    

    对于整型值,可使用NumberInt类(表示四字节带符号整数)或NumberLong类(表示八字节带符号整数),分别举例如下:

    {"x" : NumberInt("3")}
    {"x" : NumberLong("3")}
    
    • 字符串
      UTF-8字符串都可表示为字符串类型的数据
    {"x" : "foobar"}
    
    • 日期
      日期被存储为自新纪元来经过的毫秒数,不存储时区:
    {"x" : new Date()}
    
    • 正则表达式
      查询时,使用正则表达式作为限定条件,语法也与JS的正则表达式语法相同:
    {"x" : /foobar/i}
    
    • 数组
      数据列表或数据集可以表示为数组
    {"x" : ["a", "b", "c"]}
    
    • 内嵌文档
      文档可嵌套其他文档,被嵌套的文档作为父文档的值
    {"x" : {"foo" : "bar"}}
    
    • 对象id
      对象id是一个12字节的ID,是文档的唯一标识。
    {"x" : ObjectId()}
    

    还有一些不那么常用,但可能有需要的类型,包括下面这些。

    • 二进制数据
      二进制数据是一个任意字节的字符串。它不能直接在shell中使用。如果要将非UTF-8字符保存到数据库中,二进制数据是唯一的方式。

    • 代码
      查询和文档中可以包括任意JS代码

    {"x" : function(){/*...*/}}
    

    5.2 日期

    JS中,Date类型可以用作MongoDB的日期类型。创建日期对象时,应使用new Date(...),而非Date(...)。如果将构造函数作为函数进行调用(即不包括new的方式),返回的是日期的字符串表示,而非日期(Date)对象。shell根据本地时区设置显示日期对象。然而,数据库中存储的日期仅为新纪元以来的毫秒数,并未存储对应的时区。(当然,可将时区信息存储为另一个键的值)。

    5.3 数组

    数组是一组值,它既能作为有序对象(如列表、栈或队列),也能作为无无序对象(如数据集)来操作。在下面的文档中,"things"这个键的值是一个数组:

    {"things" : ["pie", 3.14]}
    

    此例表示,数组可包含不同数据类型的元素(在此,是一个字符串和一个浮点数)。实际上,常规的键值对支持的所有值都可以作为数组的值,数组中甚至可以嵌套数组。

    文档中的数组有个奇妙的特性,就是MongoDB能理解其结构,并知道如何深入数组内部对其内容进行操作。这样就能使用数组内容对数组进行查询和构建索引了。如之前的例子中,可以查询出"things"数组中包含3.14这个元素的所有文档。还可以使用原子更新对数组内容进行修改,这在后面讲解。

    5.4 内嵌文档

    文档可以作为键的值,这样的文档就是内嵌文档。如用一个文档来表示一个人,同时还要保存他的地址,可以将地址信息保存在内嵌的“address”文档中:

    {
      "name" : "Tom",
      "address" : {
        "street" : "123 Park Street",
        "city" : "Anytown",
        "state" : "NY"
      }
    }
    

    同数组一样,MongoDB能够理解内嵌文档的结构, 并能深入其中构建索引,执行查询或更新。在关系型数据库中,这个例子中的文档一般会被拆分成两个表中的两个行("people""address"各一行)。而MongoDB中直接将地址文档嵌入到人员文档中。这样做的坏处就是会导致更多的数据重复。假设"address"是关系数据库中的一个独立的表,我们需要修正地址中的错误。当我们对"people""address"执行连续操作时,使用这个地址的每个人的信息都会得到更新。但是在MongoDB中,则需要对每个人的文档分别修正拼写错误。

    5.5 _id 和 ObjectId

    MongoDB中存储的文档必须有一个"_id"键。这个键的值可以是任何类型的,默认是个ObjectId对象。在一个集合里面,每个文档都有唯一的"_id",确保集合里面每个文档都能被唯一标识。如果有两个集合的话,两个集合可以都有一个"_id"的值为123(一个数据库中集合名不能重复),但是每个集合里面只能有一个文档的"_id"值为123

    5.5.1 ObjectId

    ObjectId"_id"的默认类型。它设计成轻量型的,不同的机器都能用全局唯一的同一种方法方便的生成它。这是MongoDB不采用其他比较常规的做法(比如自动增加的主键)的主要原因,因为在多个服务器上同步自动增加主键既费时又费力。

    ObjectId使用十二字节的存储空间,是一个由二十四个十六进制数字组成的字符串(每个字节可以存储两个十六进制数字)。如果快速连续创建多个ObjectId,会发现每次只有最护几位数字有变化。另外,中间的几位数字也会变化(要是在创建的过程中停顿几秒)。这是ObjectId的创建方式导致的。其十二字节按照如下方式生成:

    6
    说明:ObjectId的前四个字节是从标准纪元开始的时间戳,单位为秒。这样会带来一些有用的属性。
    • 时间戳,与随后的五字节组合起来,提供了秒级别的唯一性。
    • 由于时间戳在前,这意味着ObjectId大致会按照插入的顺序排列。但是不是绝对的。
    • 这四字节也隐含了文档创建的事件。绝大多数驱动程序都会提供一个方法,用于从ObjectId获取这些信息。

    用户不必担心多服务器时钟同步的问题,因为时间戳的实际值并不重要,只要它总是不停增加就好了(每秒一次)。接下来的三字节是所在主机的唯一标识。通常是机器主机名的散列值。这样就可以确保不同主机生成不同的ObjectId,不产生冲突。为了确保在同一台机器上并发的多个进程产生的ObjectId是唯一的,接下来的两字节来自产生ObjectId的进程的进程标识符(PID)。

    前九字节保证了同一秒钟不同机器不同进程产生的ObjectId是唯一的。最后三字节是一个自动增加的计数器,确保相同进程同一秒产生的ObjectId也是不一样的。一秒钟最多允许每个进程拥有2563个不同的ObjectId

    5.5.2 自动生成 _id

    在创建文档的时候,如果文档没有插入"_id"键,系统会自动创建一个。

    六、使用 MongoDB shell

    在之前已经讲过,可以使用命令mongo 127.0.0.1:12345/test连接相关数据库。而同时我们启动MongoDB的时候不连接到任何MongoDB有时也很方便。通过--nodb参数启动shell,启动时就不会连接到任何数据库:mongo --nodb。启动之后,在需要时运行new Mongo(hostname)命令就可以连接到想要的MongoDB了:

    > conn = new Mongo("some-host:12345")
    connection to some-host:12345
    > db = conn.getDB("test")
    test
    

    6.1 shell 小贴士

    可以使用help命令查看相关帮助,可以通过db.help()查看数据库级别的帮助,使用db.test.help()查看集合级别的帮助。如果想知道一个函数是做什么用的,可以直接在shell输入函数名(函数名后面不要加小括号即可),这样就可以看到相应函数的JS实现代码。

    > db.test.update
    

    6.2 使用shell 执行脚本

    我们在MongoDB中可以直接执行相关的JS脚本,比如现在给出一个脚本script1.js(这里只是给出了一个脚本文件,其实可以同时给出多个脚本文件)。其内容如下:

    print("Hello World");
    

    执行时,首先启动MongoDB,然后使用命令

    sudo bin/mongo 127.0.0.1:27017/test script1.js
    

    执行。

    7
    说明:这里要注意脚本文件所在的目录(使用正确的路径),而且这里还没有连接服务器。这里使用print()函数将内容输出到标准输出,而且可以加入--quiet参数(sudo bin/mongo --quiet 127.0.0.1:27017/test script1.js)可以不打印MongoDB shell version...之类的信息。当然,在连接服务器之后也可以执行脚本文件。如下:
    8

    在脚本中可以访问db变量,以及其他全局变量。然而,shell辅助函数(如"use db")不可以在文件中使用。这些辅助函数都有对应的JS函数,如表所示:

    辅助函数 等价函数
    use foo db.getSisterDB("foo")
    show dbs db.getMongo().getDBs()
    show collections db.getCollectionNames()

    可以使用脚本将变量注入到shell。例如,下面的脚本对于本书的复制和分片部分内容非常有用。这个脚本定义了一个connectTo()函数,它连接到指定端口处的一个本地数据库,并且将db指向这个连接。
    defineConnectTo.js

    var connectTo = function(port, dbname){
        if(!port){
            port = 27017;
        }
        if(!dbname){
            dbname = "test";
        }
        db = connect("localhost:" + port + "/" + dbname);
        return db;
    };
    

    如果在shell中加载这个脚本,connectTo函数就可以使用了。

    9

    除了添加辅助函数,还可以使用脚本将通用的任务和管理活动自动化。默认情况下,shell会在运行shell时所处的目录中查找脚本(可以使用run("pwd")命令查看)。如果脚本不在当前目录中,可以为shell指定一个相对路径或者绝对路径。如可以使用load("/usr/yj-software/my-mongodb/defineConnectTo.js")来加载defineConnectTo.js。注意,load函数无法解析~符号。也可以在shell中使用run()函数来执行命令行程序。可以在函数参数列表中指定程序所需的参数:

    10
    通常来说,这种使用方式的局限性很大,因为输出格式很奇怪,而且不支持管道。

    相关文章

      网友评论

        本文标题:2、MongoDB基础知识(1)(MongoDB笔记)

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