MongoDB笔记

NoSQL简介

Not Only SQL
为了处理庞大的数据而产生
大多数是分布式的,性能高

CAP定理

一个分布式系统不可能同时满足三个需求,最多同时满足两个

  • Consistency 一致性 所有节点在同一时间具有相同的数据
  • Availability 可用性 每个请求无论成功或失败都有响应
  • Partition tolerance 分隔容忍 系统中任意信息的丢失不会影响系统的继续运作

CA:RDBMS
CP:MongoDB,HBase,Redis
AP:CouchDB,Cassandra,DynamoDB,Riak

丢失信息后,由于不存在数据库中的关系(关系的建立在程序一端),
不影响数据库的运行

ACID vs BASE

传统数据库与NoSQL数据库讲究的不同

ACID BASE
原子性(Atomicity) 基本可用(Basically Available)
一致性(Consistency) 软状态/柔性事务(Soft state)
隔离性(Isolation) 最终一致性 (Eventual consistency)
持久性 (Durable)

其中最终一致性就是将关系的赋予放在程序中,
最终结果与传统数据库相同

分类

类型 部分代表 特点
列存储 Hbase Cassandra Hypertable 顾名思义,是按列存储数据的。最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,对针对某一列或者某几列的查询有非常大的IO优势。
文档存储 MongoDB CouchDB 文档存储一般用类似json的格式存储,存储的内容是文档型的。这样也就有机会对某些字段建立索引,实现关系数据库的某些功能。
key-value存储 Tokyo Cabine t / Tyrant Berkeley DB MemcacheDB Redis 可以通过key快速查询到其value。一般来说,存储不管value的格式,照单全收。(Redis包含了其他功能)
图存储 Neo4J FlockDB 图形关系的最佳存储。使用传统关系数据库来解决的话性能低下,而且设计使用不方便。
对象存储 db4o Versant 通过类似面向对象语言的语法操作数据库,通过对象的方式存取数据。
xml数据库 Berkeley DB XML BaseX 高效的存储XML数据,并支持XML的内部查询语法,比如XQuery,Xpath。

MongoDB简介

  • 2009年2月首次推出
  • 开源
  • C++编写
  • 基于分布式文件存储(高负载时添加节点以保证性能)
  • 数据为键值对,类似JSON,称为BSON(Binary),大小限制16M
  • 检索,插入等全部依赖于键值对,以及为了扩展而使用的提示符
  • 特点是介于关系型数据库与非关系型数据库之间(非关系型数据库入门之选)

安装

windows

  • 下载安装包安装

  • 建立需要的文件夹和配置文件
    配置文件

    1
    2
    3
    4
    5
    systemLog:
    destination: file
    path: c:\data\log\mongod.log
    storage:
    dbPath: c:\data\db
  • 使用配置文件启动服务

    1
    C:\mongodb\bin\mongod.exe --config "C:\mongodb\mongod.cfg" --install
  • shell管理

    1
    mongo

linux

  • 下载解压添加PATH

  • 可以使用参数启动服务

    1
    mongod --dbpath "/usr/local/mongodata/db"
  • shell管理

    1
    mongo
  • mongod Web界面

    1
    mongod --rest

    web界面比服务端端口多1000

osx

  1. 一般安装

    下载皆有添加path

  2. brew安装

    名为mongodb
    支持SSL需要命令 --with-opensl
    开发版本需要 --devel

  3. 运行

    同其他

术语相关

对比

SQL术语 MongoDB术语 说明
database database 数据库
table collection 集合
row document 文档
column field 字段
index index 索引
table joins 使用嵌入文档实现,但更多的是在程序中实现
primary key 主键,MongoDB自动将\~id字段设置为主键~

数据库

MongoDB默认数据库为"db",对应文件夹/<somewhere>/db/
相关操作:

  • show dbs 查看所有的数据库
  • db 显示当前数据库
  • use <dbname> 使用数据库/连接数据库,没有会创建
    • 命名规则:
      • 全部小写
      • 最多64字节
      • 不能有引号,空格,句号,$,/,\,空字符
      • 不能为空
      • 避开保留名称
        • admin 权限管理相关
        • local 本地的数据
        • config 用于保存分片信息

集合

相当于表

  • 没有固定结构: 一条数据与其他数据可以有不同的键,但一般人为写成大致相同的

命名

  • 不能含有保留字符
  • 以system.开头是保留的
  • 不能含有空字符
  • 不能为空

Capped collections

  • 固定大小的集合,因此性能好
  • 列队过期(空间不足时,新的来旧的走)
  • 适合日志

文档

相当于行

  • 格式为JSON
  • 键值对是有序的
  • 区分类型和大小写
  • 可以使用其他格式的数据

键的注意

  • 不可以重复
  • 可以使用utf-8字符
  • 不能有空字符
  • .和$不能轻易使用
  • 下划线表示保留字段

元数据

每一个数据库的元数据都放在该数据库的system集合(系统保留集合)中

集合命名空间 描述
dbname.system.namespaces 列出所有名字空间。
dbname.system.indexes 列出所有索引,可插入数据
dbname.system.profile 包含数据库概要(profile)信息,可删除
dbname.system.users 列出所有可访问数据库的用户,可修改
dbname.local.sources 包含复制对端(slave)的服务器信息和状态。

数据类型

数据类型很多

数据类型 描述
String 字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的。
Integer 整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位。
Boolean 布尔值。用于存储布尔值(真/假)。
Double 双精度浮点值。用于存储浮点值。
Min/Max keys 将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比。
Array 用于将数组或列表或多个值存储为一个键。
Timestamp 时间戳。记录文档修改或添加的具体时间。
Object 用于内嵌文档。
Null 用于创建空值。
Symbol 符号。该数据类型基本上等同于字符串类型,但不同的是,它一般用于采用特殊符号类型的语言。
Date 日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息。
Object ID 对象 ID。用于创建文档的 ID。
Binary Data 二进制数据。用于存储二进制数据。
Code 代码类型。用于在文档中存储 JavaScript 代码。
Regular expression 正则表达式类型。用于存储正则表达式。
  1. ObjectId

    唯一主键,12字节
    4字节时间戳+3字节机器标识+2字节PID+3字节随机数
    有时间戳,所以并不不要为数据建立插入时间戳,可以使用objectIdObject.getTimestamp()获取

  2. Timestamp

    一个64位的值
    32位time值,远Unix新纪元相差的秒数+32位序数
    用于MongoDB内部使用,程序中用的主要是Date类型

  3. Date

    当前距离Unix新纪元的毫秒数,可以转换为格林尼治时间.
    当然也可以在程序中使用其他功能做一个包含时间信息的字符串

数据库普通操作

连接数据库

在登录mongo shell后使用

1
mongodb://username:password@host:port/dbName?options
  • username:password可省
  • port可省,默认为27017
  • dbName可省,默认为test
  • option之间使用&或;隔开

option与其他数据库的设置大致相同

选项 描述
replicaSet=name 验证replica set的名称。 Impliesconnect=replicaSet.
slaveOk=true(false) true:在connect=direct模式下,驱动会连接第一台机器,即使这台服务器不是主。在connect=replicaSet模式下,驱动会发送所有的写请求到主并且把读取操作分布在其他从服务器. {br} false: 在 connect=direct模式下,驱动会自动找寻主服务器. 在connect=replicaSet 模式下,驱动仅仅连接主服务器,并且所有的读写命令都连接到主服务器
safe=true(false) true:在执行更新操作之后,驱动都会发送getLastError命令来确保更新成功。(还要参考 wtimeoutMS). {br} false: 在每次更新之后,驱动不会发送getLastError来确保更新成功
w=n 驱动添加 { w : n } 到getLastError命令. 应用于safe=true。
wtimeoutMS=ms 驱动添加 { wtimeout : ms } 到 getlasterror 命令. 应用于 safe=true.
fsync=true(false) true: 驱动添加 { fsync : true } 到 getlasterror 命令.应用于 safe=true. {br} false: 驱动不会添加到getLastError命令中
journal=true(false) 如果设置为 true, 同步到 journal (在提交到数据库前写入到实体中). 应用于 safe=true
connectTimeoutMS=ms 可以打开连接的时间。
socketTimeoutMS=ms 发送和接受sockets的时间。

为什么不是在使用mongo进入shell时就询问用户名与密码?

其他操作

  • show dbs 查看所有的数据库
  • use <dbName> 使用数据库,没有则创建
  • db 查看当前使用的数据库
  • db.dropDatabase() 删除当前数据库
  • show collections 查看当前数据库所有集合
  • db.createCollection(name,options)
    • options的参数有

      • capped 是否固定,true时需要指定size,默认false
      • autoIndexId 是否将\~id作为索引~,默认false
      • size 在不是capped时好像也可以使用
      • max 包含文档的最大数量
    • options使用json格式定义
      举例

      1
      db.createCollection("mycol", { capped : true, autoIndexId : true, size : 6142800, max : 10000 } )
  • db.<collectionName>.drop() 删除集合
  • db.<collectionName>.insert(document) 插入文档
    • 可以在集合不存在时使用,则自动创建集合
    • 可以插入同样的两条文档
  • db.<collectionName>.update(<query>,<update>,<option>) 更新文档
    • query相当于where后的

    • update相当与set后的

    • options有

      • upsert: 如果不存在记录,是否插入?默认false
      • multi: 默认更新第一条记录,可以全部更新
      • writeContern: 抛出异常的级别
    • 举例
      例子中将title为MongoDB 教程的所有文档的title改为MongoDB

      1
      db.col.update({'title':'MongoDB 教程'},{$set:{'title':'MongoDB'}},{multi:true})
  • db.<collectionName>.save() 替换文档(也算是更新)
    • 默认以~id为检索条件~?

    • 例子中替换~id为56064f89ade2f21f36b03136~ 的文档数据

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      db.col.save({
      "_id" : ObjectId("56064f89ade2f21f36b03136"),
      "title" : "MongoDB",
      "description" : "MongoDB 是一个 Nosql 数据库",
      "by" : "Runoob",
      "url" : "http://www.runoob.com",
      "tags" : [
      "mongodb",
      "NoSQL"
      ],
      "likes" : 110
      })
  • db.<collectionName>.remove(<query>,<options>) 删除数据
    • options
      • justOne: 是否第一个,默认false
      • writeConcern: 抛出异常的级别
  • db.<collectionName.find(<query>,<projection>).pretty() 检索文档
    • projection: 结果中包含的键,默认全部
    • pretty是为了数据易读性,非必须
  • db.<collectionName>.findOne(<query>,<projection>) 检索文档,只要一个

限定语

使用诸如 $lt 等关键字表示各种操作符或者含义

  1. 简单四则

    操作 格式 范例 RDBMS中的类似语句
    等于 {<key>:<value>} db.col.find({“by”:“菜鸟教程”}).pretty() where by = ‘菜鸟教程’
    小于 {$lt:<value>} db.col.find({“likes”:{$lt:50}}).pretty() where likes < 50
    小于或等于 {$lte:<value>} db.col.find({“likes”:{$lte:50}}).pretty() where likes <= 50
    大于 {$gt:<value>} db.col.find({“likes”:{$gt:50}}).pretty() where likes > 50
    大于或等于 {$gte:<value>} db.col.find({“likes”:{$gte:50}}).pretty() where likes >= 50
    不等于 {$ne:<value>} db.col.find({“likes”:{$ne:50}}).pretty() where likes != 50
  2. 逻辑运算

    1. AND

      使用逗号间隔即可

    2. OR

      1
      $or:[{key1:value1},{key2:value2}]

      使用

      1
      2
      3
      4
      5
      6
      7
      db.col.find(
      {
      $or: [
      {key1: value1}, {key2:value2}
      ]
      }
      ).pretty()
  3. $type指定类型

    {$type: type}
    type可以是数字,也可以是type名

    类型 数字 备注
    Double 1
    String 2
    Object 3
    Array 4
    Binary data 5
    Undefined 6 已废弃。
    Object id 7
    Boolean 8
    Date 9
    Null 10
    Regular Expression 11
    JavaScript 13
    Symbol 14
    JavaScript (with scope) 15
    32-bit integer 16
    Timestamp 17
    64-bit integer 18
    Min key 255 Query with -1.
    Max key 127

    举例

    1
    db.col.find({"title" : {$type : 2}})
  4. 过滤结果

    检索结果对象的方法

    • db.<collectionName>.find().limit(N) 仅仅显示N条
    • db.<collectionName>.find().skip(N) 跳过N条

    结果还是检索结果对象,可以链式使用

    • db.col.find({},{“title”:1,~id~:0}).limit(2).skip(1)?
  5. 排序结果

    检索结果对象的方法
    db.COLLECTION~NAME~.find().sort({KEY:1})

    • 其中1表示升序,-1表示降序
  6. 聚合

    用于处理数据(求和,计算平均值等)

    1. 用法

      1
      db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)
    2. group举例

      数据
      其中按照by~user字段来统计~,runoob.com的两个,Neo4j.com一个

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      {
      _id: ObjectId(7df78ad8902c)
      title: 'MongoDB Overview',
      description: 'MongoDB is no sql database',
      by_user: 'runoob.com',
      url: 'http://www.runoob.com',
      tags: ['mongodb', 'database', 'NoSQL'],
      likes: 100
      },
      {
      _id: ObjectId(7df78ad8902d)
      title: 'NoSQL Overview',
      description: 'No sql database is very fast',
      by_user: 'runoob.com',
      url: 'http://www.runoob.com',
      tags: ['mongodb', 'database', 'NoSQL'],
      likes: 10
      },
      {
      _id: ObjectId(7df78ad8902e)
      title: 'Neo4j Overview',
      description: 'Neo4j is no sql database',
      by_user: 'Neo4j',
      url: 'http://www.neo4j.com',
      tags: ['neo4j', 'database', 'NoSQL'],
      likes: 750
      },

      表达式中相当于新定义了一个文档的结构,
      定义了新文档(结果显示用)的~id~,num~tutorial字段~
      $group表示分组,$sum表示求和,1表示?

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      > db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : 1}}}])
      {
      "result" : [
      {
      "_id" : "runoob.com",
      "num_tutorial" : 2
      },
      {
      "_id" : "Neo4j",
      "num_tutorial" : 1
      }
      ],
      "ok" : 1
      }
      >

      相当于SQL语句

      1
      select by_user, count(*) from mycol group by by_user
    3. 其他操作符

      表达式 描述 实例
      $sum 计算总和。 db.mycol.aggregate([{$group : {\~id~ : “$by\~user~”, num\~tutorial~ : {$sum : “$likes”}}}])
      $avg 计算平均值 db.mycol.aggregate([{$group : {\~id~ : “$by\~user~”, num\~tutorial~ : {$avg : “$likes”}}}])
      $min 获取集合中所有文档对应值得最小值。 db.mycol.aggregate([{$group : {\~id~ : “$by\~user~”, num\~tutorial~ : {$min : “$likes”}}}])
      $max 获取集合中所有文档对应值得最大值。 db.mycol.aggregate([{$group : {\~id~ : “$by\~user~”, num\~tutorial~ : {$max : “$likes”}}}])
      $push 在结果文档中插入值到一个数组中。 db.mycol.aggregate([{$group : {\~id~ : “$by\~user~”, url : {$push: “$url”}}}])
      $addToSet 在结果文档中插入值到一个数组中,但不创建副本。 db.mycol.aggregate([{$group : {\~id~ : “$by\~user~”, url : {$addToSet : “$url”}}}])
      $first 根据资源文档的排序获取第一个文档数据。 db.mycol.aggregate([{$group : {\~id~ : “$by\~user~”, first\~url~ : {$first : “$url”}}}])
      $last 根据资源文档的排序获取最后一个文档数据 db.mycol.aggregate([{$group : {\~id~ : “$by\~user~”, last\~url~ : {$last : “$url”}}}])
    4. 其他非求和方法

      1. 投影?

        1
        2
        3
        4
        5
        6
        db.article.aggregate(
        { $project : {
        title : 1 ,
        author : 1 ,
        }}
        );

        这样结果中就只有\~id~,tilte和author三个字段(默认情况下\~id字段是被包含~)
        如果要想不包含\~id需要显式指定~:

        1
        2
        3
        4
        5
        6
        db.article.aggregate(
        { $project : {
        _id : 0 ,
        title : 1 ,
        author : 1
        }});
      2. 匹配

        match获取了分数在70至90之间的文档,
        然后将结果通过管道送给下一个group语句处理

        1
        2
        3
        4
        db.articles.aggregate( [
        { $match : { score : { $gt : 70, $lte : 90 } } },
        { $group: { _id: null, count: { $sum: 1 } } }
        ] );

        所以语句中最外层的 [] 就是为了多条语句的管道操作准备的
        不过该 [] 不必须

      3. 跳过

        1
        db.article.aggregate({ $skip : 5 });
  7. 正则表达式

    1. 支持情况

      支持PCRE(Perl Compatible Regular Expression)

    2. 基本用法

      1
      {$regex:"runoob"}

      或者简写为

      1
      /runoob/
    3. 选项的使用

      1
      {$regex:"runoob",$options:"$i"}

      或者简写为

      1
      /runoob/i
    4. 注意

      • 索引比正则表达式快

      • 正则中使用变量需要使用eval

        1
        eval("/" + value + "/i")

索引

储存索引至RAM,加快查询速度

  1. 创建索引

    用法

    1
    db.collection.createIndex(keys, options)

    参数有

    Parameter Type Description
    background Boolean 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 “background” 可选参数。 “background” 默认值为*false*。
    unique Boolean 建立的索引是否唯一。指定为true创建唯一索引。默认值为*false*.
    name string 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。
    dropDups Boolean 3.0+版本已废弃。在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为 *false*.
    sparse Boolean 对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 *false*.
    expireAfterSeconds integer 指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。
    v index version 索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。
    weights document 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。
    default\~language~ string 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语
    language\~override~ string 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language.

    举例
    同样使用1表示升序,-1表示降序

    1
    db.values.createIndex({open: 1, close: 1}, {background: true})

数据库其他操作

复制

为了保障数据的安全性,将数据复制到另一个服务器上
主节点操作并纪录log,从节点定期读取log并采取同样的操作以保证数据一致
在设计上保证每个节点都可以是主节点,可以自动故障转移
从节点又称为副本集 replica set

举例:在27017端口创建一个名为rs0的副本集,数据存放在D盘某位置

1
mongod --port 27017 --dbpath "D:\set up\mongodb\data" --replSet rs0

还可以给副本集添加成员:rs.add(HOST~NAME~:PORT)
例如 rs.add(“mongod1.net:27017”)

不过只有主节点可以添加,判断是否为主节点可以使用 db.isMaster()

分片

请求数量大时,将对一个服务器的请求分散到多个服务器上以减轻压力
或者数据过多,一个服务器存储不下
MongoDB的分片集群结构中有三个角色

  • Router 前端粮油,使整个集群看起来像一个
  • Config Server 存储其他信息?
  • Shard(replica set) 实际存储数据
  1. 举例

    1. 服务器设计如下

      Shard Server 1:27020
      Shard Server 2:27021
      Shard Server 3:27022
      Shard Server 4:27023
      Config Server :27100
      Route Process:40000

    2. 分别启动服务器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      # shared server
      mongod --port 27020 --dbpath=/www/mongoDB/shard/s0 --logpath=/www/mongoDB/shard/log/s0.log --logappend --fork
      mongod --port 27021 --dbpath=/www/mongoDB/shard/s1 --logpath=/www/mongoDB/shard/log/s1.log --logappend --fork
      mongod --port 27022 --dbpath=/www/mongoDB/shard/s2 --logpath=/www/mongoDB/shard/log/s2.log --logappend --fork
      mongod --port 27023 --dbpath=/www/mongoDB/shard/s3 --logpath=/www/mongoDB/shard/log/s3.log --logappend --fork
      # config server
      mongod --port 27100 --dbpath=/www/mongoDB/shard/config --logpath=/www/mongoDB/shard/log/config.log --logappend --fork
      # route process
      mongos --port 40000 --configdb localhost:27100 --fork --logpath=/www/mongoDB/shard/log/route.log --chunkSize 500

      其中chunkSize单位为MB,默认200MB

    3. 配置sharding

      1
      2
      3
      4
      5
      6
      7
      8
      # 登录
      mongo admin --port 40000
      # 添加节点
      db.runCommand({ addshard:"localhost:27020" })
      # 设置被分片的数据库
      db.runCommand({ enablesharding:"test" })
      # 什么配置?
      db.runCommand({ shardcollection: "test.log", key: { id:1,time:1}})
    4. 使用

      正常使用40000接口的数据库即可

备份与恢复

数据导出到指定目录或从目录导入数据库的操作

  1. 备份

    1
    mongodump -h dbhost -d dbname -o dbdirectory
    • host可以带端口号,如127.0.0.1:27017
    • 也可以使用port选项指定端口
    • 不指定dbnmae会默认备份所有DB
      • 若指定dbname,会在指定路径下自动创建一个同被备份数据库同名的文件夹
    • collection选项可以指定集合名
  2. 恢复

    1
    mongorestore -h <hostname><:port> -d dbname <path>
    • –host <:port>, -h <:port>
      MongoDB所在服务器地址,默认为: localhost:27017
    • –db, -d
      需要恢复的数据库实例,例如:test,当然这个名称也可以和备份时候的不一样,比如test2
    • –drop
      恢复的时候,先删除当前数据,然后恢复备份的数据。备份后添加修改的数据都会被删除
    • –dir
      指定备份的目录
    • <path>
      mongorestore最后的一个参数,设置备份数据所在位置,例如
      c:\data\dump\test,需要指定数据库名对应的文件夹
      不能同时指定 <path> 和 --dir 选项,–dir也可以设置备份目录。

监控

用于了解性能

  • mongostat 看各种stat
  • mongotop 看那些查询花大量的时间

数据库高级操作

关系

指多个文档在逻辑上的关系
可以有几种关系

  • 1:1
  • 1:N
  • N:1
  • N:N

有两种方法建立关系

  • 嵌入
  • 引用

比如用户Tom Hanks住在Los Angeles
分别有不关联的信息:
用户集合

1
2
3
4
5
6
{
"_id":ObjectId("52ffc33cd85242f436000001"),
"name": "Tom Hanks",
"contact": "987654321",
"dob": "01-01-1991"
}

地址集合

1
2
3
4
5
6
7
{
"_id":ObjectId("52ffc4a5d85242602e000000"),
"building": "22 A, Indiana Apt",
"pincode": 123456,
"city": "Los Angeles",
"state": "California"
}
  1. 嵌入

    直接手动/程序写入到数据中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
       "_id":ObjectId("52ffc33cd85242f436000001"),
    "contact": "987654321",
    "dob": "01-01-1991",
    "name": "Tom Benzamin",
    "address": [
    {
    "building": "22 A, Indiana Apt",
    "pincode": 123456,
    "city": "Los Angeles",
    "state": "California"
    },
    {
    "building": "170 A, Acropolis Apt",
    "pincode": 456789,
    "city": "Chicago",
    "state": "Illinois"
    }]
    }

    使用简单

    1
    db.users.findOne({"name":"Tom Benzamin"},{"address":1})
  2. 引用

    1. 手动引用

      通过ObjectId来引用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      {
      "_id":ObjectId("52ffc33cd85242f436000001"),
      "contact": "987654321",
      "dob": "01-01-1991",
      "name": "Tom Benzamin",
      "address_ids": [
      ObjectId("52ffc4a5d85242602e000000"),
      ObjectId("52ffc4a5d85242602e000001")
      ]
      }

      使用时为了获取信息需要查询两次

      1
      2
      var result = db.users.findOne({"name":"Tom Benzamin"},{"address_ids":1})
      var addresses = db.address.find({"_id":{"$in":result["address_ids"]}})

      其中第二句手动指定了要查询的集合address

    2. DBRefs

      一个文档从多个集合引用文档时使用
      用起来像是外键,

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      {
      "_id":ObjectId("53402597d852426020000002"),
      "address": {
      "$ref": "address_home", // 集合名称
      "$id": ObjectId("534009e4d852427820000002"), // id
      "$db": "runoob"}, // db名称
      "contact": "987654321",
      "dob": "01-01-1991",
      "name": "Tom Benzamin"
      }

      数据库本身仍然没有任何联系,
      只是方便了程序调用

      1
      2
      3
      var user = db.users.findOne({"name":"Tom Benzamin"})
      var dbRef = user.address
      db[dbRef.$ref].findOne({"_id":(dbRef.$id)}) // 注意这里使用了db[collectionName]的形式指定使用的集合

覆盖索引查询

若查询条件是索引,并且结果在同一个索引中?
借助在RAM中的索引,
可以使用覆盖索引查询大大地加快查询的速度.

举例
若一个集合大致结构如下

1
2
3
4
5
6
7
8
{
"_id": ObjectId("53402597d852426020000002"),
"contact": "987654321",
"dob": "01-01-1991",
"gender": "M",
"name": "Tom Benzamin",
"user_name": "tombenzamin"
}

且此集合已经添加了索引

1
db.users.ensureIndex({gender:1,user_name:1})

使用以下查询时可以很快
这里指定了以user~name排序~,且不要输出id

1
db.users.find({gender:"M"},{user_name:1,_id:0})

若使用以下查询,由于id没有指定时为默认输出,而id又不是索引的一部分
查询会慢

1
db.users.find({gender:"M"},{user_name:1})

查询分析

用于分析索引的设置是否得当

  • explain()
    提供了索引及查询统计
    用法: db.collectionName.find({something}).explain()
    输出一些信息

  • hint()
    在检索时强制使用某些索引

    1
    db.users.find({gender:"M"},{user_name:1,_id:0}).hint({gender:1,user_name:1})

原子操作

MongoDB不支持事务,也不要求数据有完整性,但还是提供了原子操作
保存,修改,删除等,都是.

为了保证关联的集合也是同步更新的,通常加入其他字段用于程序判断

原子操作重用的指令有

  • $set
  • $unset
  • $int
  • $push
  • $pushAll
  • $pull
  • $addToSet
  • $pop
  • $rename
  • $bit

高级索引

  1. 数组作为索引

    为数组建立索引时,会自动将数组中的每个元素都建立索引
    查询时可以变快
    数据:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    {
    "address": {
    "city": "Los Angeles",
    "state": "California",
    "pincode": "123"
    },
    "tags": [
    "music",
    "cricket",
    "blogs"
    ],
    "name": "Tom Benzamin"
    }

    建立索引

    1
    db.users.ensureIndex({"tags":1})

    查询

    1
    db.users.find({tags:"cricket"})

    验证

    1
    db.users.find({tags:"cricket"}).explain()

    结果中显示 “cursor” : “BtreeCursor tags~1~” ,则表示已经使用了索引

  2. 子文档作为索引

    上例中若想将address这个子文档作为索引,需要对每个子文档的元素都建立索引
    这里使用了点的写法,表示索引属于子文档

    1
    db.users.ensureIndex({"address.city":1,"address.state":1,"address.pincode":1})

    检索数据时也使用点的写法
    不必关注检索时使用关键词的顺序

    1
    db.users.find({"address.state":"California","address.city":"Los Angeles"}) 
  3. 全文检索

    存储对象是文章时,会为文章中每个词都建立索引,记录词的位置和次数.
    支持英语在内的部分表声文字
    默认开启全文检索功能.
    可以将保存文章的字段作为索引
    比如有数据

    1
    2
    3
    4
    5
    6
    7
    {
    "post_text": "enjoy the mongodb articles on Runoob",
    "tags": [
    "mongodb",
    "runoob"
    ]
    }

    可以这样建立索引

    1
    db.posts.ensureIndex({post_text:"text"})

    使用时,似乎不必指明需要查询哪个索引,但这里使用了$text和$search,功能不明

    1
    db.posts.find({$text:{$search:"runoob"}})

    删除索引:

    1
    2
    3
    4
    # 查询
    db.<collectionName>.getIndexs
    # 删除
    db.<collectionName>.dropIndex("xxxxxxxx")

索引限制

  • 索引占空间,读取操作不多时不要使用索引
  • 索引不能被一些操作符相关的操作使用
    • 非操作符 $nin,$not
    • 算术 $mod
    • $where子句
  • 字段占空间太大将不会被创建索引
  • 索引不能超过64个
  • 名称长度不能超过128字符
  • 符合索引最多31个字段

ObjectId

4时间戳+3机器标识码+2PID+3随机数
共12字节
不使用常规主键,原因是在多个服务器上同步增加主键费时费力

Map Reduce

将大批量的数据分解执行(MAP),然后将结果合并成最终结果(REDUCE).
用法

1
2
3
4
5
6
7
8
9
10
db.collection.mapReduce(
function() {emit(key,value);}, //map 函数,挑选key与value作为reduce函数的参数
function(key,values) {return reduceFunction}, //reduce 函数,将map传来的values计算为value,传来的id可能没什么用,也可能作为了分组的依据
{
out: collection, # 存放结果的集合,不指定则使用临时集合
query: document, # 指定检索条件
sort: document, # 排序条件
limit: number # 限制条数
}
)

举例
插入同样意义的数据,其中active5条(其中数据mark的4条,数据runoob的1条),disable3条

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
>db.posts.insert({
"post_text": "菜鸟教程,最全的技术文档。",
"user_name": "mark",
"status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
"post_text": "菜鸟教程,最全的技术文档。",
"user_name": "mark",
"status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
"post_text": "菜鸟教程,最全的技术文档。",
"user_name": "mark",
"status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
"post_text": "菜鸟教程,最全的技术文档。",
"user_name": "mark",
"status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
"post_text": "菜鸟教程,最全的技术文档。",
"user_name": "mark",
"status":"disabled"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
"post_text": "菜鸟教程,最全的技术文档。",
"user_name": "runoob",
"status":"disabled"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
"post_text": "菜鸟教程,最全的技术文档。",
"user_name": "runoob",
"status":"disabled"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
"post_text": "菜鸟教程,最全的技术文档。",
"user_name": "runoob",
"status":"active"
})
WriteResult({ "nInserted" : 1 })

命令中要求按照user~name分别统计active的数量~

1
2
3
4
5
6
7
8
db.posts.mapReduce( 
function() { emit(this.user_name,1); },
function(key, values) {return Array.sum(values)},
{
query:{status:"active"},
out:"post_total"
}
)

结果

1
2
3
4
5
6
7
8
9
10
11
{
"result" : "post_total", # 计算结果
"timeMillis" : 23, # 花费的事件,毫秒
"counts" : {
"input" : 5, # 满足条件的个数
"emit" : 5, # emit调用次数
"reduce" : 1, #
"output" : 2 # 结果文档数
},
"ok" : 1 # 成功则为1
}

post~total的内容~

1
2
{ "_id" : "mark", "value" : 4 }
{ "_id" : "runoob", "value" : 1 }

GridFS

  1. 介绍

    用于存储超过16M(BSON文件限制)的文件
    将大文件分割成小块chunk,一般256k一个,所有的chunk被作为文档保存在chunks集合中
    除此之外还将文件有关的metadate放在file集合中
    比如
    files集合中的文档

    1
    2
    3
    4
    5
    6
    7
    8
    {
    "_id": ObjectId("534a75d19f54bfec8a2fe44b"),
    "filename": "test.txt",
    "chunkSize": NumberInt(261120),
    "uploadDate": ISODate("2014-04-13T11:32:33.557Z"),
    "md5": "7b762939321e146569b07f72c62cca4f",
    "length": NumberInt(646)
    }

    chunks集合中的一个文档
    注意id是对应的

    1
    2
    3
    4
    5
    {
    "files_id": ObjectId("534a75d19f54bfec8a2fe44b"),
    "n": NumberInt(0),
    "data": "Mongo Binary Data"
    }
  2. 添加文件

    1
    mongofiles -d gridfs put song.mp3

    gridfs是一个命令集,put是其命令

  3. 查看添加的文件

    默认放在了fs(集合的集合,理论上可以有无限个集合嵌套)下,

    1
    db.fs.files.find({filename:"song.mp3"})

    得到

    1
    2
    3
    4
    5
    6
    7
    {
    _id: ObjectId('534a811bf8b4aa4d33fdf94d'),
    filename: "song.mp3",
    chunkSize: 261120,
    uploadDate: new Date(1397391643474), md5: "e4f53379c909f7bed2e9d631e15c1c41",
    length: 10401959
    }

    然后到chunks中查找相同的fileid

    1
    db.fs.chunks.find({files_id:ObjectId('534a811bf8b4aa4d33fdf94d')})

固定集合

  1. 创建

    指定了size和最大的文档数

    1
    db.createCollection("cappedLogCollection",{capped:true,size:10000,max:1000})
  2. 判断

    1
    db.cappedLogCollection.isCapped()
  3. 转换已存在的集合至固定集合

    1
    db.runCommand({"convertToCapped":"posts",size:10000})
  4. 查询时注意

    返回的顺序要么是插入顺序,要么是反向

    1
    db.cappedLogCollection.find().sort({$natural:-1})
  5. 其他特点

    • 可以插入和更新,但不允许超出size,否则失败
    • 不允许删除一条记录,但可以删除整个集合

自动增长

  • ~id允许自定义值~
  • 需自行在程序中建立增加的代码,并且在DB中加入一个用于记录自增id的集合

举例

  1. 用于计数的复制集合例

    其~id的命名是为了方便地找到counters的位置~,
    sequence~value就是真正记录自增id的字段~

    1
    2
    3
    4
    {
    "_id":"productid",
    "sequence_value": 0
    }
  2. 程序中的自增代码

    带有查找并更新的功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function getNextSequenceValue(sequenceName){
    var sequenceDocument = db.counters.findAndModify(
    {
    query:{_id: sequenceName },
    update: {$inc:{sequence_value:1}},
    "new":true
    });
    return sequenceDocument.sequence_value;
    }
  3. 正常使用的过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    db.products.insert({
    "_id":getNextSequenceValue("productid"),
    "product_name":"Apple iPhone",
    "category":"mobiles"})

    db.products.insert({
    "_id":getNextSequenceValue("productid"),
    "product_name":"Samsung S3",
    "category":"mobiles"})

    结果如下

    1
    2
    3
    { "_id" : 1, "product_name" : "Apple iPhone", "category" : "mobiles"}

    { "_id" : 2, "product_name" : "Samsung S3", "category" : "mobiles" }

与其他程序的API

java

PHP

PHP7

Node.js

管理工具

监控工具

  • Munin 插件
  • Gangila 插件
  • Cacti 插件

GUI工具

  • Fang of Mongo 网页格式
  • Futon4Mongo
  • Mongo3 Ruby写成
  • MongoHub OSX平台
  • Opricot 基于浏览器,由PHP写成
  • Database Master window平台
  • RockMongo PHP5写成