MongoDB文档笔记

一、NoSQL简介

关系型数据库遵循ACID原则

事务在英文中是transaction,和现实世界中的交易很类似,它有如下四个特性:

1、A (Atomicity) 原子性

原子性很容易理解,也就是说事务里的所有操作要么全部做完,要么都不做,事务成功的条件是事务里的所有操作都成功,只要有一个操作失败,整个事务就失败,需要回滚。

比如银行转账,从A账户转100元至B账户,分为两个步骤:1)从A账户取100元;2)存入100元至B账户。这两步要么一起完成,要么一起不完成,如果只完成第一步,第二步失败,钱会莫名其妙少了100元。

2、C (Consistency) 一致性

一致性也比较容易理解,也就是说数据库要一直处于一致的状态,事务的运行不会改变数据库原本的一致性约束。

例如现有完整性约束a+b=10,如果一个事务改变了a,那么必须得改变b,使得事务结束后依然满足a+b=10,否则事务失败。

3、I (Isolation) 独立性

所谓的独立性是指并发的事务之间不会互相影响,如果一个事务要访问的数据正在被另外一个事务修改,只要另外一个事务未提交,它所访问的数据就不受未提交事务的影响。

比如现有有个交易是从A账户转100元至B账户,在这个交易还未完成的情况下,如果此时B查询自己的账户,是看不到新增加的100元的。

4、D (Durability) 持久性

持久性是指一旦事务提交后,它所做的修改将会永久的保存在数据库上,即使出现宕机也不会丢失。

分布式系统

分布式系统(distributed system)由多台计算机和通信的软件组件通过计算机网络连接(本地网络或广域网)组成。分布式系统是建立在网络之上的软件系统。正是因为软件的特性,所以分布式系统具有高度的内聚性和透明性。因此,网络和分布式系统之间的区别更多的在于高层软件(特别是操作系统),而不是硬件。分布式系统可以应用在在不同的平台上如:Pc、工作站、局域网和广域网上等。

分布式计算的优点

可靠性(容错) :

分布式计算系统中的一个重要的优点是可靠性。一台服务器的系统崩溃并不影响到其余的服务器。

可扩展性:

在分布式计算系统可以根据需要增加更多的机器。

资源共享:

共享数据是必不可少的应用,如银行,预订系统。

灵活性:

由于该系统是非常灵活的,它很容易安装,实施和调试新的服务。

更快的速度:

分布式计算系统可以有多台计算机的计算能力,使得它比其他系统有更快的处理速度。

开放系统:

由于它是开放的系统,本地或者远程都可以访问到该服务。

更高的性能:

相较于集中式计算机网络集群可以提供更高的性能(及更好的性价比)。

分布式计算的缺点

故障排除: 

故障排除和诊断问题。

软件: 

更少的软件支持是分布式计算系统的主要缺点。

网络:

网络基础设施的问题,包括:传输问题,高负载,信息丢失等。

安全性: 

开发系统的特性让分布式计算系统存在着数据的安全性和共享的风险等问题。

什么是NoSQL?

NoSQL,指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。

NoSQL用于超大规模数据的存储。(例如谷歌或Facebook每天为他们的用户收集万亿比特的数据)。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。

为什么使用NoSQL ?

今天我们可以通过第三方平台(如:Google,Facebook等)可以很容易的访问和抓取数据。用户的个人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加。我们如果要对这些用户数据进行挖掘,那SQL数据库已经不适合这些应用了, NoSQL数据库的发展也却能很好的处理这些大的数据。

RDBMS vs NoSQL

RDBMS 

- 高度组织化结构化数据 

- 结构化查询语言(SQL) (SQL) 

- 数据和关系都存储在单独的表中。 

- 数据操纵语言,数据定义语言 

- 严格的一致性

- 基础事务

NoSQL 

- 代表着不仅仅是SQL

- 没有声明性查询语言

- 没有预定义的模式

-键 - 值对存储,列存储,文档存储,图形数据库

- 最终一致性,而非ACID属性

- 非结构化和不可预知的数据

- CAP定理 

- 高性能,高可用性和可伸缩性

 

NoSQL一词最早出现于1998年,是Carlo Strozzi开发的一个轻量、开源、不提供SQL功能的关系数据库。

2009年,Last.fm的Johan Oskarsson发起了一次关于分布式开源数据库的讨论[2],来自Rackspace的Eric Evans再次提出了NoSQL的概念,这时的NoSQL主要指非关系型、分布式、不提供ACID的数据库设计模式。

2009年在亚特兰大举行的"no:sql(east)"讨论会是一个里程碑,其口号是"select fun, profit from real_world where relational=false;"。因此,对NoSQL最普遍的解释是"非关联型的",强调Key-Value Stores和文档数据库的优点,而不是单纯的反对RDBMS。

CAP定理(CAP theorem)

在计算机科学中, CAP定理(CAP theorem), 又被称作 布鲁尔定理(Brewer's theorem), 它指出对于一个分布式计算系统来说,不可能同时满足以下三点:

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

CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。

因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:

  • CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
  • CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
  • AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。

NoSQL的优点/缺点

优点:

  • - 高可扩展性
  • - 分布式计算
  • - 低成本
  • - 架构的灵活性,半结构化数据
  • - 没有复杂的关系

缺点:

  • - 没有标准化
  • - 有限的查询功能(到目前为止)
  • - 最终一致是不直观的程序

BASE

BASE:Basically Available, Soft-state, Eventually Consistent。 由 Eric Brewer 定义。

CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。

BASE是NoSQL数据库通常对可用性及一致性的弱要求原则:

Basically Availble --基本可用

Soft-state --软状态/柔性事务。 "Soft state" 可以理解为"无连接"的, 而 "Hard state" 是"面向连接"的

Eventual Consistency --最终一致性 最终一致性, 也是是 ACID 的最终目的。

ACID vs BASE

ACID

BASE

原子性(Atomicity)

基本可用(Basically Available)

一致性(Consistency)

软状态/柔性事务(Soft state)

隔离性(Isolation)

最终一致性 (Eventual consistency)

持久性 (Durable)

 

NoSQL 数据库分类

类型

部分代表

特点

列存储

Hbase

Cassandra

Hypertable

顾名思义,是按列存储数据的。最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,对针对某一列或者某几列的查询有非常大的IO优势。

文档存储

MongoDB

CouchDB

文档存储一般用类似json的格式存储,存储的内容是文档型的。这样也就有有机会对某些字段建立索引,实现关系数据库的某些功能。

key-value存储

Tokyo Cabinet / Tyrant

Berkeley DB

MemcacheDB

Redis

可以通过key快速查询到其value。一般来说,存储不管value的格式,照单全收。(Redis包含了其他功能)

图存储

Neo4J

FlockDB

图形关系的最佳存储。使用传统关系数据库来解决的话性能低下,而且设计使用不方便。

对象存储

db4o

Versant

通过类似面向对象语言的语法操作数据库,通过对象的方式存取数据。

xml数据库

Berkeley DB XML

BaseX

高效的存储XML数据,并支持XML的内部查询语法,比如XQuery,Xpath。

二、MongoDB简介

什么是MongoDB ?

MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统。

在高负载的情况下,添加更多的节点,可以保证服务器性能。

MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。

MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

主要特点

  • MongoDB的提供了一个面向文档存储,操作起来比较简单和容易。
  • 你可以在MongoDB记录中设置任何属性的索引 (如:FirstName="Sameer",Address="8 Gandhi Road")来实现更快的排序。
  • 你可以通过本地或者网络创建数据镜像,这使得MongoDB有更强的扩展性。
  • 如果负载的增加(需要更多的存储空间和更强的处理能力) ,它可以分布在计算机网络中的其他节点上这就是所谓的分片。
  • Mongo支持丰富的查询表达式。查询指令使用JSON形式的标记,可轻易查询文档中内嵌的对象及数组。
  • MongoDb 使用update()命令可以实现替换完成的文档(数据)或者一些指定的数据字段 。
  • Mongodb中的Map/reduce主要是用来对数据进行批量处理和聚合操作。
  • Map和Reduce。Map函数调用emit(key,value)遍历集合中所有的记录,将key与value传给Reduce函数进行处理。
  • Map函数和Reduce函数是使用Javascript编写的,并可以通过db.runCommand或mapreduce命令来执行MapReduce操作。
  • GridFS是MongoDB中的一个内置功能,可以用于存放大量小文件。
  • MongoDB允许在服务端执行脚本,可以用Javascript编写某个函数,直接在服务端执行,也可以把函数的定义存储在服务端,下次直接调用即可。
  • MongoDB支持各种编程语言:RUBY,PYTHON,JAVA,C++,PHP,C#等多种语言。
  • MongoDB安装简单。

历史 2007年10月,MongoDB由10gen团队所发展。2009年2月首度推出。

  • 2012年05月23日,MongoDB2.1 开发分支发布了! 该版本采用全新架构,包含诸多增强。
  • 2012年06月06日,MongoDB 2.0.6 发布,分布式文档数据库。
  • 2013年04月23日,MongoDB 2.4.3 发布,此版本包括了一些性能优化,功能增强以及bug修复。
  • 2013年08月20日,MongoDB 2.4.6 发布,是目前最新的稳定版。

MongoDB 工具

有几种可用于MongoDB的管理工具。

监控

MongoDB提供了网络和系统监控工具Munin,它作为一个插件应用于MongoDB中。

Gangila是MongoDB高性能的系统监视的工具,它作为一个插件应用于MongoDB中。

基于图形界面的开源工具 Cacti, 用于查看CPU负载, 网络带宽利用率,它也提供了一个应用于监控 MongoDB 的插件。

GUI

  • Fang of Mongo – 网页式,由Django和jQuery所构成。
  • Futon4Mongo – 一个CouchDB Futon web的mongodb山寨版。
  • Mongo3 – Ruby写成。
  • MongoHub – 适用于OSX的应用程序。
  • Opricot – 一个基于浏览器的MongoDB控制台, 由PHP撰写而成。
  • Database Master — Windows的mongodb管理工具
  • RockMongo — 最好的PHP语言的MongoDB管理工具,轻量级, 支持多国语言.

三、MongoDB 概念解析

 

在mongodb中基本的概念是文档、集合、数据库,下面我们挨个介绍。

下表将帮助您更容易理解Mongo中的一些概念:

SQL术语/概念

MongoDB术语/概念

解释/说明

database

database

数据库

table

collection

数据库表/集合

row

document

数据记录行/文档

column

field

数据字段/域

index

index

索引

table joins

 

表连接,MongoDB不支持

primary key

primary key

主键,MongoDB自动将_id字段设置为主键

通过下图实例,我们也可以更直观的的了解Mongo中的一些概念:

数据库

一个mongodb中可以建立多个数据库。

MongoDB的默认数据库为"db",该数据库存储在data目录中。

MongoDB的单个实例可以容纳多个独立的数据库,每一个都有自己的集合和权限,不同的数据库也放置在不同的文件中。

"show dbs" 命令可以显示所有数据的列表。

  1. $ ./mongo  MongoDB shell version: 3.0.6  connecting to: test  > show dbs  local  0.078GB  test   0.078GB  >  

执行 "db" 命令可以显示当前数据库对象或集合。

 

  1. $ ./mongo  MongoDB shell version: 3.0.6  connecting to: test  > db  test  >  

运行"use"命令,可以连接到一个指定的数据库。

 

  1. > use local  switched to db local  > db  local  >  

以上实例命令中,"local" 是你要链接的数据库。

在下一个章节我们将详细讲解MongoDB中命令的使用。

数据库也通过名字来标识。数据库名可以是满足以下条件的任意UTF-8字符串。

  • 不能是空字符串("")。
  • 不得含有' '(空格)、.、$、/、\和\0 (空宇符)。
  • 应全部小写。
  • 最多64字节。

有一些数据库名是保留的,可以直接访问这些有特殊作用的数据库。

  • admin: 从权限的角度来看,这是"root"数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。
  • local: 这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合
  • config: 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。

文档

文档是一个键值(key-value)对(即BSON)。MongoDB 的文档不需要设置相同的字段,并且相同的字段不需要相同的数据类型,这与关系型数据库有很大的区别,也是 MongoDB 非常突出的特点。

一个简单的文档例子如下:

  1. {"site":"www.runoob.com", "name":"菜鸟教程"}

下表列出了 RDBMS 与 MongoDB 对应的术语:

RDBMS

MongoDB

数据库

数据库

表格

集合

文档

字段

表联合

嵌入文档

主键

主键 (MongoDB 提供了 key 为 _id )

数据库服务和客户端

 

Mysqld/Oracle

mongod

mysql/sqlplus

mongo

需要注意的是:

  1. 文档中的键/值对是有序的。
  2. 文档中的值不仅可以是在双引号里面的字符串,还可以是其他几种数据类型(甚至可以是整个嵌入的文档)。
  3. MongoDB区分类型和大小写。
  4. MongoDB的文档不能有重复的键。
  5. 文档的键是字符串。除了少数例外情况,键可以使用任意UTF-8字符。

文档键命名规范:

  • 键不能含有\0 (空字符)。这个字符用来表示键的结尾。
  • .和$有特别的意义,只有在特定环境下才能使用。
  • 以下划线"_"开头的键是保留的(不是严格要求的)。

集合

集合就是 MongoDB 文档组,类似于 RDBMS (关系数据库管理系统:Relational Database Management System)中的表格。

集合存在于数据库中,集合没有固定的结构,这意味着你在对集合可以插入不同格式和类型的数据,但通常情况下我们插入集合的数据都会有一定的关联性。

比如,我们可以将以下不同数据结构的文档插入到集合中:

  1. {"site":"www.baidu.com"}  {"site":"www.google.com","name":"Google"}  {"site":"www.runoob.com","name":"菜鸟教程","num":5} 

当第一个文档插入时,集合就会被创建。

合法的集合名

  • 集合名不能是空字符串""。
  • 集合名不能含有\0字符(空字符),这个字符表示集合名的结尾。
  • 集合名不能以"system."开头,这是为系统集合保留的前缀。
  • 用户创建的集合名字不能含有保留字符。有些驱动程序的确支持在集合名里面包含,这是因为某些系统生成的集合中包含该字符。除非你要访问这种系统创建的集合,否则千万不要在名字里出现$。

如下实例:

  1. db.col.findOne() 

capped collections

Capped collections 就是固定大小的collection。

它有很高的性能以及队列过期的特性(过期按照插入的顺序). 有点和 "RRD" 概念类似。

Capped collections是高性能自动的维护对象的插入顺序。它非常适合类似记录日志的功能 和标准的collection不同,你必须要显式的创建一个capped collection, 指定一个collection的大小,单位是字节。collection的数据存储空间值提前分配的。

要注意的是指定的存储大小包含了数据库的头信息。

  1. db.createCollection("mycoll", {capped:true, size:100000})
  • 在capped collection中,你能添加新的对象。
  • 能进行更新,然而,对象不会增加存储空间。如果增加,更新就会失败 。
  • 数据库不允许进行删除。使用drop()方法删除collection所有的行。
  • 注意: 删除之后,你必须显式的重新创建这个collection。
  • 在32bit机器中,capped collection最大存储为1e9( 1X109)个字节。

元数据

数据库的信息是存储在集合中。它们使用了系统的命名空间:

  1. dbname.system.* 

在MongoDB数据库中名字空间 <dbname>.system.* 是包含多种系统信息的特殊集合(Collection),如下:

集合命名空间

描述

dbname.system.namespaces

列出所有名字空间。

dbname.system.indexes

列出所有索引。

dbname.system.profile

包含数据库概要(profile)信息。

dbname.system.users

列出所有可访问数据库的用户。

dbname.local.sources

包含复制对端(slave)的服务器信息和状态。

对于修改系统集合中的对象有如下限制。

在{{system.indexes}}插入数据,可以创建索引。但除此之外该表信息是不可变的(特殊的drop index命令将自动更新相关信息)。

{{system.users}}是可修改的。 {{system.profile}}是可删除的。

MongoDB 数据类型

下表为MongoDB中常用的几种数据类型。

数据类型

描述

String

字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的。

Integer

整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位。

Boolean

布尔值。用于存储布尔值(真/假)。

Double

双精度浮点值。用于存储浮点值。

Min/Max keys

将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比。

Arrays

用于将数组或列表或多个值存储为一个键。

Timestamp

时间戳。记录文档修改或添加的具体时间。

Object

用于内嵌文档。

Null

用于创建空值。

Symbol

符号。该数据类型基本上等同于字符串类型,但不同的是,它一般用于采用特殊符号类型的语言。

Date

日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息。

Object ID

对象 ID。用于创建文档的 ID。

Binary Data

二进制数据。用于存储二进制数据。

Code

代码类型。用于在文档中存储 JavaScript 代码。

Regular expression

正则表达式类型。用于存储正则表达式。

四、MongoDB连接

启动 MongoDB服务

你只需要在MongoDB安装目录的bin目录下执行'mongod'即可启动MongoDB服务。

执行启动操作后,mongodb在输出一些必要信息后不会输出任何信息,之后就等待连接的建立,当连接被建立后,就会开始打印日志信息。

你可以使用 MongoDB shell 来连接 MongoDB 服务器。你也可以使用 PHP 来连接 MongoDB。本教程我们会使用 MongoDB shell 来连接 Mongodb 服务,之后的章节我们将会介绍如何通过php 来连接MongoDB服务。

通过shell连接MongoDB服务

你可以通过执行以下命令来连接MongoDB的服务。

注意:localhost为主机名,这个选项是必须的:

mongodb://localhost

当你执行以上命令时,你可以看到以下输出结果:

  1. $ ./mongo  MongoDB shell version: 3.0.6  connecting to: test  > mongodb://localhostmongodb://localhost  ...  

这时候你返回查看运行 ./mongod 命令的窗口,可以看到是从哪里连接到MongoDB的服务器,您可以看到如下信息:

……省略信息……  2015-09-25T17:22:27.336+0800 I CONTROL  [initandlisten] allocator: tcmalloc  2015-09-25T17:22:27.336+0800 I CONTROL  [initandlisten] options: { storage: { dbPath: "/data/db" } }  2015-09-25T17:22:27.350+0800 I NETWORK  [initandlisten] waiting for connections on port 27017  2015-09-25T17:22:36.012+0800 I NETWORK  [initandlisten] connection accepted from 127.0.0.1:37310 #1 (1 connection now open)  # 该行表明一个来自本机的连接    ……省略信息…… 

MongoDB连接命令格式

使用用户名和密码连接到MongoDB服务器,你必须使用 'username:[email protected]/dbname' 格式,'username'为用户名,'password' 为密码。

使用用户名和密码连接登陆到默认数据库:

$ ./mongo  MongoDB shell version: 3.0.6  connecting to: test  mongodb://admin:[email protected]

以上命令中,用户 admin 使用密码 123456 连接到本地的 MongoDB 服务上。输出结果如下所示:<、p>

> mongodb://admin:[email protected]/  ...  

使用用户名和密码连接登陆到指定数据库:

连接到指定数据库的格式如下:

mongodb://admin:[email protected]/test

更多连接实例

连接本地数据库服务器,端口是默认的。

mongodb://localhost

使用用户名fred,密码foobar登录localhost的admin数据库。

mongodb://fred:[email protected]

使用用户名fred,密码foobar登录localhost的baz数据库。

mongodb://fred:[email protected]/baz

连接 replica pair, 服务器1为example1.com服务器2为example2。

mongodb://example1.com:27017,example2.com:27017

连接 replica set 三台服务器 (端口 27017, 27018, 和27019):

mongodb://localhost,localhost:27018,localhost:27019

连接 replica set 三台服务器, 写入操作应用在主服务器 并且分布查询到从服务器。

mongodb://host1,host2,host3/?slaveOk=true

直接连接第一个服务器,无论是replica set一部分或者主服务器或者从服务器。

mongodb://host1,host2,host3/?connect=direct;slaveOk=true

当你的连接服务器有优先级,还需要列出所有服务器,你可以使用上述连接方式。

安全模式连接到localhost:

mongodb://localhost/?safe=true

以安全模式连接到replica set,并且等待至少两个复制服务器成功写入,超时时间设置为2秒。

mongodb://host1,host2,host3/?safe=true;w=2;wtimeoutMS=2000

参数选项说明

标准格式:

mongodb://[username:[email protected]]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]

标准的连接格式包含了多个选项(options),如下所示:

选项

描述

replicaSet=name

验证replica set的名称。 Impliesconnect=replicaSet.

slaveOk=true|false

  • true:在connect=direct模式下,驱动会连接第一台机器,即使这台服务器不是主。在connect=replicaSet模式下,驱动会发送所有的写请求到主并且把读取操作分布在其他从服务器。
  • false: 在 connect=direct模式下,驱动会自动找寻主服务器. 在connect=replicaSet 模式下,驱动仅仅连接主服务器,并且所有的读写命令都连接到主服务器。

safe=true|false

  • true: 在执行更新操作之后,驱动都会发送getLastError命令来确保更新成功。(还要参考 wtimeoutMS).

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.
  • false: 驱动不会添加到getLastError命令中。

journal=true|false

如果设置为 true, 同步到 journal (在提交到数据库前写入到实体中). 应用于 safe=true

connectTimeoutMS=ms

可以打开连接的时间。

socketTimeoutMS=ms

发送和接受sockets的时间。

五、数据增删

1、MongoDB 创建数据库

MongoDB 创建数据库的语法格式如下:

  1. use DATABASE_NAME

如果数据库不存在,则创建数据库,否则切换到指定数据库。

实例

以下实例我们创建了数据库 mongo:

  1. > use mongo 
  1. switched to db mongo 
  1. > db
  1. mongo
  1. >  

如果你想查看所有数据库,可以使用 show dbs 命令:

  1. > show dbs 
  1. local  0.078GB 
  1. test   0.078GB 
  1. >  

可以看到,我们刚创建的数据库 mongo 并不在数据库的列表中, 要显示它,我们需要向 mongo 数据库插入一些数据。

  1. > db.mongo.insert({"name":"mongodb中文网"})
  1. WriteResult({ "nInserted" : 1 }) 
  1. > show dbs 
  1. local   0.078GB 
  1. mongo  0.078GB
  2. test    0.078GB 
  1. >  

MongoDB 中默认的数据库为 test,如果你没有创建新的数据库,集合将存放在 test 数据库中。

2、MongoDB 删除数据库

MongoDB 删除数据库的语法格式如下:

  1. db.dropDatabase()

删除当前数据库,默认为 test,你可以使用 db 命令查看当前数据库名。

实例

以下实例我们删除了数据库 mongo。

首先,查看所有数据库:

  1. > show dbs 
  1. local   0.078GB 
  1. mongo   0.078GB 
  2. test    0.078GB

接下来我们切换到数据库 mongo

  1. > use runoob  switched to db mongo  >

执行删除命令:

  1. > db.dropDatabase()  { "dropped" : "runoob", "ok" : 1 }

最后,我们再通过 show dbs 命令数据库是否删除成功:

  1. > show dbs 
  1. local  0.078GB 
  1. test   0.078GB 
  1. >

删除集合

集合删除语法格式如下:

  1. db.collection.drop() 

3、MongoDB 插入文档

文档的数据结构和JSON基本一样。

所有存储在集合中的数据都是BSON格式。

BSON是一种类json的一种二进制形式的存储格式,简称Binary JSON。

插入文档

MongoDB 使用 insert() 或 save() 方法向集合中插入文档,语法如下:

  1. db.COLLECTION_NAME.insert(document) 

实例

以下文档可以存储在 MongoDB 的 mongo 数据库 的 col集合中:

  1. >db.col.insert({title: 'MongoDB 教程',
  1. description: 'MongoDB 是一个 Nosql 数据库',
  1. by: 'MongoDB中文网',
  1. url: 'http://www.mongodb.org.cn',
  2. tags: ['mongodb', 'database', 'NoSQL'],
  3. likes: 100 
  1. })

以上实例中 col 是我们的集合名,前一章节我们已经创建过了,如果该集合不在该数据库中, MongoDB 会自动创建该集合并插入文档。

查看已插入文档:

  1. > db.col.find() 
  2. {
  1. "_id" : ObjectId("56064886ade2f21f36b03134"),
  2. "title" : "MongoDB 教程",
  3. "description" : "MongoDB 是一个 Nosql 数据库",
  4. "by" : "MongoDB中文网",
  5. "url" : "http://www.mongodb.org.cn",
  6. "tags" : [ "mongodb", "database", "NoSQL" ],
  7. "likes" : 100
  1. }
  2. >  

我们也可以将数据定义为一个变量,如下所示:

  1. > document=({title: 'MongoDB 教程',
  1. description: 'MongoDB 是一个 Nosql 数据库',
  1. by: 'Mongodb中文网',   
  1. url: 'http://www.mongodb.org.cn',
  2. tags: ['mongodb', 'database', 'NoSQL'], 
  3. likes: 100 
  1. }); 

执行后显示结果如下:

  1. {         
  1. "title" : "MongoDB 教程", 
  2. "description" : "MongoDB 是一个 Nosql 数据库",
  3. "by" : "Mongodb中文网",       
  4. "url" : "http://www.mongodb.org.cn", 
  5. "tags" : [               
  6. "mongodb",          
  7. "database",       
  8. "NoSQL"      
  1. ],      
  1. "likes" : 100 
  1. } 

执行插入操作:

  1. > db.col.insert(document)  WriteResult({ "nInserted" : 1 })  >  

插入文档你也可以使用 db.col.save(document) 命令。如果不指定 _id 字段 save() 方法类似于 insert() 方法。如果指定 _id 字段,则会更新该 _id 的数据。

4、MongoDB 更新文档

MongoDB 使用 update()  save() 方法来更新集合中的文档。接下来让我们详细来看下两个函数的应用及其区别。

update() 方法

update() 方法用于更新已存在的文档。语法格式如下:

  1. db.collection.update(   
  1. <query>,
  2. <update>,
  1. {      
  1. upsert: <boolean>,  
  2. multi: <boolean>, 
  3. writeConcern: <document>
  1. }
  2. )

参数说明:

  • query : update的查询条件,类似sql update查询内where后面的。
  • update : update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql update查询内set后面的
  • upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
  • multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
  • writeConcern :可选,抛出异常的级别。

实例

我们在集合 col 中插入如下数据:

  1. >db.col.insert({ 
  1. title: 'Mongodb 教程', 
  2. description: 'MongoDB 是一个 Nosql 数据库',  
  1. by: 'Mongodb中文网',  
  1. url: 'http://www.mongodb.org.cn',  
  2. tags: ['mongodb', 'database', 'NoSQL'], 
  3. likes: 100 
  1. })

接着我们通过 update() 方法来更新标题(title):

  1. >db.col.update({'title':'MongoDB 教程'},{$set:{'title':'MongoDB'}})
  1. WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })   # 输出信息 
  1. > db.col.find().pretty() 
  2. {        
  1. "_id" : ObjectId("56064f89ade2f21f36b03136"), 
  2. "title" : "MongoDB",  
  3. "description" : "MongoDB 是一个 Nosql 数据库", 
  4. "by" : "Mongodb中文网",     
  5. "url" : "http://www.mongodb.org.cn",
  6. "tags" : [  
  7. "mongodb",  
  8. "database",  
  9. "NoSQL"     
  1. ],     
  1. "likes" : 100 
  1. } 
  2. > 

可以看到标题(title)由原来的 "MongoDB 教程" 更新为了 "MongoDB"。

以上语句只会修改第一条发现的文档,如果你要修改多条相同的文档,则需要设置 multi 参数为 true。

  1. >db.col.update({'title':'MongoDB 教程'},{$set:{'title':'MongoDB'}},{multi:true})

save() 方法

save() 方法通过传入的文档来替换已有文档。语法格式如下:

  1. db.collection.save(   
  1. <document>,    
  1. {     
  1. writeConcern: <document>
  1. } 
  2. ) 

参数说明:

  • document : 文档数据。
  • writeConcern :可选,抛出异常的级别。

实例

以下实例中我们替换了 _id 为 56064f89ade2f21f36b03136 的文档数据:

  1. >db.col.save({          
  1. "_id" : ObjectId("56064f89ade2f21f36b03136"),  
  2. "title" : "MongoDB", 
  3. "description" : "MongoDB 是一个 Nosql 数据库",
  4. "by" : "MongoDB中文网",  
  5. "url" : "http://www.mongodb.org.cn",    
  6. "tags" : [          
  7. "mongodb",      
  8. "NoSQL"   
  1. ],   
  1. "likes" : 110
  1. }
  2. )

替换成功后,我们可以通过 find() 命令来查看替换后的数据

  1. >db.col.find().pretty()
  2. {         
  1. "_id" : ObjectId("56064f89ade2f21f36b03136"), 
  2. "title" : "MongoDB",  
  3. "description" : "MongoDB 是一个 Nosql 数据库",
  4. "by" : "Mongo",        
  5. "url" : "http://www.mongodb.org.cn",  
  6. "tags" : [               
  7. "mongodb",     
  8. "NoSQL"       
  1. ],     
  1. "likes" : 110 
  1. } 
  2. >

更多实例

只更新第一条记录:

  1. db.col.update( { "count" : { $gt : 1 } } , { $set : { "test2" : "OK"} } );

全部更新:

  1. db.col.update( { "count" : { $gt : 3 } } , { $set : { "test2" : "OK"} },false,true );

只添加第一条:

  1. db.col.update( { "count" : { $gt : 4 } } , { $set : { "test5" : "OK"} },true,false );

全部添加加进去:

  1. db.col.update( { "count" : { $gt : 5 } } , { $set : { "test5" : "OK"} },true,true );

全部更新:

  1. db.col.update( { "count" : { $gt : 15 } } , { $inc : { "count" : 1} },false,true );

只更新第一条记录:

  1. db.col.update( { "count" : { $gt : 10 } } , { $inc : { "count" : 1} },false,false );

5、删除文档

MongoDB remove()函数是用来移除集合中的数据。

MongoDB数据更新可以使用update()函数。在执行remove()函数前先执行find()命令来判断执行的条件是否正确,这是一个比较好的习惯。

语法

remove() 方法的基本语法格式如下所示:

  1. db.collection.remove(
  1. <query>,    
  2. <justOne>
  1. ) 

如果你的 MongoDB 是 2.6 版本以后的,语法格式如下:

  1. db.collection.remove(    
  1. <query>,    
  1. {      
  1. justOne: <boolean>,
  2. writeConcern: <document>
  1. }
  2. )

参数说明:

  • query :(可选)删除的文档的条件。
  • justOne : (可选)如果设为 true 或 1,则只删除一个文档。
  • writeConcern :(可选)抛出异常的级别。

实例

以下文档我们执行两次插入操作:

  1. >db.col.insert({title: 'MongoDB 教程',
  1. description: 'MongoDB 是一个 Nosql 数据库',
  1. by: 'MongoDB中文网',  
  1. url: 'http://www.mongodb.org.cn',
  2. tags: ['mongodb', 'database', 'NoSQL'],
  3. likes: 100
  1. })

使用 find() 函数查询数据:

  1. > db.col.find() 
  2. { "_id" : ObjectId("56066169ade2f21f36b03137"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "MongoDB中文网", "url" : "http://www.mongodb.org.cn", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 100 } 
  3. { "_id" : ObjectId("5606616dade2f21f36b03138"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "MongoDB中文网", "url" : "http://www.mongodb.org.cn", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 100 } 

接下来我们移除 title 为 'MongoDB 教程' 的文档:

  1. >db.col.remove({'title':'MongoDB 教程'})  WriteResult({ "nRemoved" : 2 })           # 删除了两条数据  >db.col.find()  ……                                        # 没有数据 

如果你只想删除第一条找到的记录可以设置 justOne 为 1,如下所示:

  1. >db.COLLECTION_NAME.remove(DELETION_CRITERIA,1)

如果你想删除所有数据,可以使用以下方式(类似常规 SQL 的 truncate 命令):

  1. >db.col.remove({}) 
  2. >db.col.find() 
  3. >

六、聚合

MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果。有点类似sql语句中的 count(*)。

aggregate() 方法

MongoDB中聚合的方法使用aggregate()。

语法

aggregate() 方法的基本语法格式如下所示:

  1. >db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION) 

实例

集合中的数据如下:

  1. {    
  1. _id: ObjectId(7df78ad8902c)   
  2. title: 'MongoDB Overview',     
  3. description: 'MongoDB is no sql database',  
  4. by_user: 'w3cschool.cc',  
  5. url: 'http://www.w3cschool.cc', 
  6. tags: ['mongodb', 'database', 'NoSQL'], 
  7. likes: 100 
  1. },
  2. { 
  1. _id: ObjectId(7df78ad8902d)    
  2. title: 'NoSQL Overview',   
  3. description: 'No sql database is very fast',   
  4. by_user: 'w3cschool.cc',  
  5. url: 'http://www.w3cschool.cc',  
  6. tags: ['mongodb', 'database', 'NoSQL'],  
  7. likes: 10 
  1. }, 
  2. {
  1. _id: ObjectId(7df78ad8902e)   
  2. title: 'Neo4j Overview', 
  3. description: 'Neo4j is no sql database',  
  4. by_user: 'Neo4j',  
  5. url: 'http://www.neo4j.com', 
  6. tags: ['neo4j', 'database', 'NoSQL'], 
  7. likes: 750 
  1. },

现在我们通过以上集合计算每个作者所写的文章数,使用aggregate()计算结果如下:

  1. > db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : 1}}}])
  2. {   
  1. "result" : [     
  1. {         
  1. "_id" : "w3cschool.cc",      
  2. "num_tutorial" : 2    
  1. },      
  2. {        
  1. "_id" : "Neo4j",      
  2. "num_tutorial" : 1    
  1. }    
  2. ],   
  1. "ok" : 1
  1. } 
  2. > 

以上实例类似sql语句: select by_user, count(*) from mycol group by by_user

在上面的例子中,我们通过字段by_user字段对数据进行分组,并计算by_user字段相同值的总和。

下表展示了一些聚合的表达式:

表达式

描述

实例

$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"}}}])

管道的概念

管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数。

MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。

表达式:处理输入文档并输出。表达式是无状态的,只能用于计算当前聚合管道的文档,不能处理其它的文档。

这里我们介绍一下聚合框架中常用的几个操作:

  • $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
  • $match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。
  • $limit:用来限制MongoDB聚合管道返回的文档数。
  • $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
  • $unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
  • $group:将集合中的文档分组,可用于统计结果。
  • $sort:将输入文档排序后输出。
  • $geoNear:输出接近某一地理位置的有序文档。

管道操作符实例

1、$project实例

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

这样的话结果中就只还有_id,tilte和author三个字段了,默认情况下_id字段是被包含的,如果要想不包含_id话可以这样:

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

2.$match实例

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

$match用于获取分数大于70小于或等于90记录,然后将符合条件的记录送到下一阶段$group管道操作符进行处理。

3.$skip实例

  1. db.article.aggregate(     
  1. { $skip : 5 });  

经过$skip管道操作符处理后,前五个文档被"过滤"掉。

Map-Reduce是一种计算模型,简单的说就是将大批量的工作(数据)分解(MAP)执行,然后再将结果合并成最终结果(REDUCE)。

MongoDB提供的Map-Reduce非常灵活,对于大规模数据分析也相当实用。

MapReduce 命令

以下是MapReduce的基本语法:

>db.collection.mapReduce(
   function() {emit(key,value);},  //map 函数
   function(key,values) {return reduceFunction},   //reduce 函数
   {
      out: collection,
      query: document,
      sort: document,
      limit: number
  
}
)

使用 MapReduce 要实现两个函数 Map 函数和 Reduce 函数,Map 函数调用 emit(key, value), 遍历 collection 中所有的记录, 将key 与 value 传递给 Reduce 函数进行处理。

Map 函数必须调用 emit(key, value) 返回键值对。

参数说明:

  • map :映射函数 (生成键值对序列,作为 reduce 函数参数)。
  • reduce 统计函数,reduce函数的任务就是将key-values变成key-value,也就是把values数组变成一个单一的值value。
  • out 统计结果存放集合 (不指定则使用临时集合,在客户端断开后自动删除)。
  • query 一个筛选条件,只有满足条件的文档才会调用map函数。(query。limit,sort可以随意组合)
  • sort 和limit结合的sort排序参数(也是在发往map函数前给文档排序),可以优化分组机制
  • limit 发往map函数的文档数量的上限(要是没有limit,单独使用sort的用处不大)

使用 MapReduce

考虑以下文档结构存储用户的文章,文档存储了用户的 user_name 和文章的 status 字段:

>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 })

现在,我们将在 posts 集合中使用 mapReduce 函数来选取已发布的文章(status:"active"),并通过user_name分组,计算每个用户的文章数:

>db.posts.mapReduce(
   function() { emit(this.user_name,1); },
   function(key, values) {return Array.sum(values)},
      { 
         query
:{status:"active"}, 
        
out:"post_total"
      }
)

以上 mapReduce 输出结果为:

{
        "result" : "post_total",
        "timeMillis" : 23,
        "counts" : {
                "input" : 5,
                "emit" : 5,
                "reduce" : 1,
                "output" : 2
        },
        "ok" : 1
}

结果表明,共有4个符合查询条件(status:"active")的文档, 在map函数中生成了4个键值对文档,最后使用reduce函数将相同的键值分为两组。

具体参数说明:

  • result:储存结果的collection的名字,这是个临时集合,MapReduce的连接关闭后自动就被删除了。
  • timeMillis:执行花费的时间,毫秒为单位
  • input:满足条件被发送到map函数的文档个数
  • emit:在map函数中emit被调用的次数,也就是所有集合中的数据总量
  • ouput:结果集合中的文档个数(count对调试非常有帮助)
  • ok:是否成功,成功为1
  • err:如果失败,这里可以有失败原因,不过从经验上来看,原因比较模糊,作用不大

使用 find 操作符来查看 mapReduce 的查询结果:

>db.posts.mapReduce(
   function() { emit(this.user_name,1); },
   function(key, values) {return Array.sum(values)},
      { 
         query
:{status:"active"}, 
        
out:"post_total"
      }
).find()

以上查询显示如下结果,两个用户 tom 和 mark 有两个发布的文章:

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

用类似的方式,MapReduce可以被用来构建大型复杂的聚合查询。

Map函数和Reduce函数可以使用 JavaScript 来实现,使得MapReduce的使用非常灵活和强大。

七、高级查询

1、索引

索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。

索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构

ensureIndex() 方法

MongoDB使用 ensureIndex() 方法来创建索引。

语法

ensureIndex()方法基本语法格式如下所示:

 

  1. >db.COLLECTION_NAME.ensureIndex({KEY:1}) 

语法中 Key 值为你要创建的索引字段,1为指定按升序创建索引,如果你想按降序来创建索引指定为-1即可。

实例

  1. >db.col.ensureIndex({"title":1})  > 

ensureIndex() 方法中你也可以设置使用多个字段创建索引(关系型数据库中称作复合索引)。

 

  1. >db.col.ensureIndex({"title":1,"description":-1})  > 

ensureIndex() 接收可选参数,可选参数列表如下:

Parameter

Type

Description

background

Boolean

建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 "background" 可选参数。 "background" 默认值为false

unique

Boolean

建立的索引是否唯一。指定为true创建唯一索引。默认值为false.

name

string

索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。

dropDups

Boolean

在建立唯一索引时是否删除重复记录,指定 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. db.values.ensureIndex({open: 1, close: 1}, {background: true}) 

通过在创建索引时加background:true 的选项,让创建工作在后台执行

2、MongoDB 高级索引

考虑以下文档集合(users ):

  1. {    
  1. "address": {      
  2. "city": "Los Angeles",    
  3. "state": "California",   
  4. "pincode": "123"   
  1. },   
  1. "tags": [       
  2. "music",      
  3. "cricket",    
  4. "blogs"    
  1. ],
  1. "name": "Tom Benzamin"
  1. }

以上文档包含了 address 子文档和 tags 数组。

索引数组字段

假设我们基于标签来检索用户,为此我们需要对集合中的数组 tags 建立索引。

在数组中创建索引,需要对数组中的每个字段依次建立索引。所以在我们为数组 tags 创建索引时,会为 music、cricket、blogs三个值建立单独的索引。

使用以下命令创建数组索引:

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

创建索引后,我们可以这样检索集合的 tags 字段:

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

为了验证我们使用使用了索引,可以使用 explain 命令:

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

以上命令执行结果中会显示 "cursor" : "BtreeCursor tags_1" ,则表示已经使用了索引。

索引子文档字段

假设我们需要通过city、state、pincode字段来检索文档,由于这些字段是子文档的字段,所以我们需要对子文档建立索引。

为子文档的三个字段创建索引,命令如下:

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

一旦创建索引,我们可以使用子文档的字段来检索数据:

  1. >db.users.find({"address.city":"Los Angeles"})    

记住查询表达式必须遵循指定的索引的顺序。所以上面创建的索引将支持以下查询:

  1. >db.users.find({"address.city":"Los Angeles","address.state":"California"})  

同样支持以下查询:

  1. >db.users.find({"address.city":"LosAngeles","address.state":"California","address.pincode":"123"}) 

3MongoDB 索引限制

额外开销

每个索引占据一定的存储空间,在进行插入,更新和删除操作时也需要对索引进行操作。所以,如果你很少对集合进行读取操作,建议不使用索引。

内存(RAM)使用

由于索引是存储在内存(RAM)中,你应该确保该索引的大小不超过内存的限制。

如果索引的大小大于内存的限制,MongoDB会删除一些索引,这将导致性能下降。

查询限制

索引不能被以下的查询使用:

  • 正则表达式及非操作符,如 $nin, $not, 等。
  • 算术运算符,如 $mod, 等。
  • $where 子句

所以,检测你的语句是否使用索引是一个好的习惯,可以用explain来查看。

索引键限制

从2.6版本开始,如果现有的索引字段的值超过索引键的限制,MongoDB中不会创建索引。

插入文档超过索引键限制

如果文档的索引字段值超过了索引键的限制,MongoDB不会将任何文档转换成索引的集合。与mongorestore和mongoimport工具类似。

最大范围

  • 集合中索引不能超过64个
  • 索引名的长度不能超过125个字符
  • 一个复合索引最多可以有31个字段

八、分片

分片

在Mongodb里面存在另一种集群,就是分片技术,可以满足MongoDB数据量大量增长的需求。

当MongoDB存储海量的数据时,一台机器可能不足以存储数据,也可能不足以提供可接受的读写吞吐量。这时,我们就可以通过在多台机器上分割数据,使得数据库系统能存储和处理更多的数据。

为什么使用分片

  • 复制所有的写入操作到主节点
  • 延迟的敏感数据会在主节点查询
  • 单个副本集限制在12个节点
  • 当请求量巨大时会出现内存不足。
  • 本地磁盘不足
  • 垂直扩展价格昂贵

MongoDB分片

下图展示了在MongoDB中使用分片集群结构分布:

上图中主要有如下所述三个主要组件:

  • Shard:
    用于存储实际的数据块,实际生产环境中一个shard server角色可由几台机器组个一个relica set承担,防止主机单点故障
  • Config Server:
    mongod实例,存储了整个 ClusterMetadata,其中包括 chunk信息。
  • Query Routers:
    前端路由,客户端由此接入,且让整个集群看上去像单一数据库,前端应用可以透明使用。

分片实例

分片结构端口分布如下:

  1. Shard Server 127020
  2. Shard Server 227021
  3. Shard Server 327022
  4. Shard Server 427023
  5. Config Server 27100
  6. Route Process40000

步骤一:启动Shard Server

  1. [[email protected] /]# mkdir -p /www/mongoDB/shard/s0
  2. [[email protected] /]# mkdir -p /www/mongoDB/shard/s1
  3. [[email protected] /]# mkdir -p /www/mongoDB/shard/s2
  4. [[email protected] /]# mkdir -p /www/mongoDB/shard/s3
  5. [[email protected] /]# mkdir -p /www/mongoDB/shard/log
  6. [[email protected] /]# /usr/local/mongoDB/bin/mongod --port 27020 --dbpath=/www/mongoDB/shard/s0 --logpath=/www/mongoDB/shard/log/s0.log --logappend --fork
  7. ....
  8. [[email protected] /]# /usr/local/mongoDB/bin/mongod --port 27023 --dbpath=/www/mongoDB/shard/s3 --logpath=/www/mongoDB/shard/log/s3.log --logappend --fork

步骤二: 启动Config Server

  1. [[email protected] /]# mkdir -p /www/mongoDB/shard/config
  2. [[email protected] /]# /usr/local/mongoDB/bin/mongod --port 27100 --dbpath=/www/mongoDB/shard/config --logpath=/www/mongoDB/shard/log/config.log --logappend --fork

注意:这里我们完全可以像启动普通mongodb服务一样启动,不需要添加—shardsvr和configsvr参数。因为这两个参数的作用就是改变启动端口的,所以我们自行指定了端口就可以。

步骤三: 启动Route Process

  1. /usr/local/mongoDB/bin/mongos --port 40000 --configdb localhost:27100 --fork --logpath=/www/mongoDB/shard/log/route.log --chunkSize 500

mongos启动参数中,chunkSize这一项是用来指定chunk的大小的,单位是MB,默认大小为200MB.

步骤四: 配置Sharding

接下来,我们使用MongoDB Shell登录到mongos,添加Shard节点

  1. [[email protected] shard]# /usr/local/mongoDB/bin/mongo admin --port 40000
  1. MongoDB shell version: 2.0.7
  1. connecting to: 127.0.0.1:40000/admin
  2. mongos> db.runCommand({ addshard:"localhost:27020" })
  1. { "shardAdded" : "shard0000", "ok" : 1 }
  2. ......
  1. mongos> db.runCommand({ addshard:"localhost:27029" })
  1. { "shardAdded" : "shard0009", "ok" : 1 }
  1. mongos> db.runCommand({ enablesharding:"test" }) #设置分片存储的数据库
  1. { "ok" : 1 }
  1. mongos> db.runCommand({ shardcollection: "test.log", key: { id:1,time:1}})
  1. { "collectionsharded" : "test.log", "ok" : 1 }

步骤五: 程序代码内无需太大更改,直接按照连接普通的mongo数据库那样,将数据库连接接入接口40000

九、副本集

1、MongoDB 副本集

MongoDB复制是将数据同步在多个服务器的过程。

复制提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性, 并可以保证数据的安全性。

复制还允许您从硬件故障和服务中断中恢复数据。

MongoDB复制原理

mongodb的复制至少需要两个节点。其中一个是主节点,负责处理客户端请求,其余的都是从节点,负责复制主节点上的数据。

mongodb各个节点常见的搭配方式为:一主一从、一主多从。

主节点记录在其上的所有操作oplog,从节点定期轮询主节点获取这些操作,然后对自己的数据副本执行

这些操作,从而保证从节点的数据与主节点一致。

MongoDB复制结构图如下所示:

MongoDB文档笔记

以上结构图总,客户端总主节点读取数据,在客户端写入数据到主节点是, 主节点与从节点进行数据交互保障数据的一致性。

MongoDB 副本集可用分两种:

Master-Slave 主从复制

实现数据同步只需要在某一台服务器启动时加上"-master"参数,以指明此服务器的角色是primary;另一台服务器加上"-slave"和"-source"参数,以指明此服务器的角色是slave。

主从复制的优点如下:

  • 从服务器可以执行查询工作,降低主服务器访问压力。
  • 在从服务器执行备份,避免备份期间锁定主服务器的数据。
  • 当主服务器出现故障时,可以快速切换到从服务器,减少当机时间。

注意:MongoDB 的最新版本已不再推荐此方案。主从复制虽然可以承受一定的负载压力,但这种方式仍然是一个单点,如果主库挂了,数据写入就成了风险。

Replica Sets复制集

MongoDB 在 1.6 版本对开发了新功能replica set,这比之前的replication 功能要强大一 些,增加了故障自动切换和自动修复成员节点,各个DB 之间数据完全一致,大大降低了维 护成功。auto shard 已经明确说明不支持replication paris,建议使用replica set,replica set 故障切换完全自动。

Replica Sets的结构类似一个集群,完全可以把它当成一个集群,因为它确实与集群实现的作用是一样的:如果其中一个节点出现故障,其他节点马上会将业务接管过来而无须停机操作。

2、MongoDB部署Replica Sets

本节演示在1台服务器上部署3节点的Replica Sets,配置信息如下表所示。

配置信息

SERVER-1

SERVER-2

SERVER-3

数据文件存储路径

/data/data/r0

/data/data/r1

/data/data/r2

日志文件存储路径

/data/log/r0.log

/data/log/r1.log

/data/log/r2.log

复制集key文件

/data/key/r0

/data/key/r1

/data/key/r2

示例监听端口

28010

28011

28012

分别创建3个节点各自的数据文件存储路径。

如下面的代码所示:

  1. [[email protected]~]#mkdir-p/data/data/r0
  2. [[email protected]~]#mkdir-p/data/data/r1
  3. [[email protected]~]#mkdir-p/data/data/r2

以上代码创建3个目录,其中SERVER-1使用"/data/data/r0"目录存储数据文件,SERVER-2使用"/data/data/r1"目录存储数据文件,SERVER-3使用"/data/data/r2"目录存储数据文件,具体如表11-1中“数据文件存储路径”部分所示。

创建日志文件存储路径。

如下面的代码所示:

  1. [[email protected]~]#mkdir-p/data/log

在本例中,创建"/data/log"目录用于存储3个节点的系统日志文件,具体如表11-1中“日志文件存储路径”部分所示。

 

创建复制集key文件存储路径。

key文件用于标识同一复制集的私钥,如果3个节点的key文件内容不一致,复制集将不能正常使用。如下面的代码所示:

  1. [[email protected]~]#mkdir-p/data/key
  2. [[email protected]~]#echo"this is rs1 super secret key">/data/key/r0
  3. [[email protected]~]#echo"this is rs1 super secret key">/data/key/r1
  4. [[email protected]~]#echo"this is rs1 super secret key">/data/key/r2
  5. [[email protected]~]#chmod 600/data/key/r*

以上代码创建3个文件用于存储复制集的key信息,其中SERVER-1使用"/data/key/r0" key文件,SERVER-2使用"/data/key/r1" key文件,SERVER-3使用"/data/key/r2" key文件,具体如表11-1中“复制集key文件”部分所示。

启动3个MongoDB实例来模拟3个节点。

如下面的代码所示:

  1. [[email protected]~]#/Apps/mongo/bin/mongod--replSet rs1--keyFile
  2. /data/key/r0--fork--port 28010--dbpath/data/data/r0--
  1. logpath=/data/log/r0.log--logappend
  2. all output going to:/data/log/r0.log
  3. forked process:6573
  1. [[email protected]~]#/Apps/mongo/bin/mongod--replSet rs1--keyFile
  2. /data/key/r1--fork--port 28011--dbpath/data/data/r1--
  1. logpath=/data/log/r1.log--logappend
  2. all output going to:/data/log/r1.log
  3. forked process:6580
  1. [[email protected]~]#/Apps/mongo/bin/mongod--replSet rs1--keyFile
  2. /data/key/r2--fork--port 28012--dbpath/data/data/r2--
  1. logpath=/data/log/r2.log--logappend
  2. all output going to:/data/log/r2.log
  3. forked process:6585
  1. [[email protected]~]#

在本例中,启动3个MongoDB实例来模拟3个节点,通过执行以下命令来启动MongoDB实例,模拟SERVER-1节点:

  1. /Apps/mongo/bin/mongod--replSet rs1--keyFile/data/key/r0--fork--port
  1. 28010--dbpath/data/data/r0--logpath=/data/log/r0.log--logappend

参数说明:

  • replSet:指明复制集的名称。本例的取值是"rs1",其他的节点也必须起这个名字才能保证3个节点间的连通性。
  • keyFile:复制集key文件的路径,对于本例它的取值是"/data/key/r0",其他的节点也必须起这个名字才能保证3个节点间的连通性。
  • fork:将命令放在后台执行。
  • port:MongoDB的监听端口,用于接收客户端请求。
  • dbpath:数据文件存储路径。
  • logpath:系统日志文件存放的位置。
  • logappend:明确指明日志的写入模式是追加,而非覆盖方式。

另外两个节点的配置与SERVER-1类似。

配置节点信息,并初始化Replica Sets环境。

如下面的代码所示:

  1. [[email protected] bin]#/Apps/mongo/bin/mongo-port 28010
  1. MongoDB shell version:1.8.1
  1. connecting to:127.0.0.1:28010/test
  1. >config_rs1={_id:'rs1',members:[
  2. ...{_id:0,host:'localhost:28010'},
  3. ...{_id:1,host:'localhost:28011'},
  4. ...{_id:2,host:'localhost:28012'}]
  5. ...
  6. }
  7. >rs.initiate(config_rs1);
  8. {
  1. "info":"Config now saved locally.Should come online in about a minute.",
  2. "ok":1
  1. }

在本例中,首先通过执行"/Apps/mongo/bin/mongo-port 28010"命令连接到SERVER-1实例;然后通过执行"config_rs1={_id:'rs1',members:……}"命令来命名Replica Sets配置的名字,指定Replica Sets 3个节点的信息。其中,参数"id"指明Replica Sets的名字,本例的值是"rs1"。接下来通过执行"rs.initiate(config_rs1)"命令启动Replica Sets,其中参数"config_rs1"就是Replica Sets配置的名字。

配置Replica Sets时需要注意priority(优先级)的概念。当priority=0时,说明这个实例永远不可能被设置成primary。也就是说,它只能作为一个slave而存在,即使在主库当机的情况下,它也不能被选为主库。这种方式其实与最原始的Master-Slave方式是一致的。例如下面的配置中,将28010和28012这两个端口的实例优先级调成0,系统就只能选28011作为主库,如下面的代码所示(加粗部分):

  1. config_rs1={_id:'rs1',members:[
  1. {_id:0,host:'localhost:28010',priority:0},
  2. {_id:1,host:'localhost:28011'},
  3. {_id:2,host:'localhost:28012',priority:0}]
  4. }

这样的设置在某些场景中还是非常有实际应用价值的。

启动复制集

复制集启动后,可以通过查看复制集状态,分析复制集的各项运行指标。如下面的代码所示:

  1. >rs.status()
  2. {
  1. "set":"rs1",
  2. "date":ISODate("2012-05-31T09:49:57Z"),
  3. "myState":1,
  4. "members":[
  1. {
  1. "_id":0,
  2. "name":"localhost:28010",
  3. "health":1,
  1. //1表明状态正常;0表明状态异常
  1. "state":1,
  1. //1表明是primary;2表明是slave
  1. "stateStr":"PRIMARY",//表明此机器是主库
  2. "optime":{
  3. "t":1338457763000,
  4. "i":1
  1. },
  1. "optimeDate":ISODate("2012-05-31T09:49:23Z"),
  2. "self":true
  1. },
  2. {
  1. "_id":1,
  2. "name":"localhost:28011",
  3. "health":1,
  4. "state":2,
  5. "stateStr":"SECONDARY",
  6. "uptime":23,
  7. "optime":{
  8. "t":1338457763000,
  9. "i":1
  1. },
  1. "optimeDate":ISODate("2012-05-31T09:49:23Z"),
  2. "lastHeartbeat":ISODate("2012-05-31T09:49:56Z")
  1. },
  2. {
  1. "_id":2,
  2. "name":"localhost:28012",
  3. "health":1,
  4. "state":2,
  5. "stateStr":"SECONDARY",
  6. "uptime":23,
  7. "optime":{
  8. "t":1338457763000,
  9. "i":1
  1. },
  1. "optimeDate":ISODate("2012-05-31T09:49:23Z"),
  2. "lastHeartbeat":ISODate("2012-05-31T09:49:56Z")
  1. }
  2. ],
  1. "ok":1
  1. }
  1. rs1:PRIMARY>

在本例中,通过执行"rs.status"命令可以查看复制集中各成员节点的状态。其中,字段"health"是实例正常与否的标记,“1”代表状态正常,“0”代表状态异常;字段"state"用于表明实例在复制集中充当的角色,“1”代表primary,“0”代表slave;字段"stateStr"进一步用文字描述了此实例在复制集中的角色,本例中的值是"PRIMARY"。

除了用"rs.status"命令可以查看复制集中各成员节点的状态之外,还可以用"isMaster"命令查看Replica Sets状态,如下面的代码所示:

  1. rs1:PRIMARY>rs.isMaster()
  1. {
  1. "setName":"rs1",
  2. "ismaster":true,
  3. "secondary":false,
  4. "hosts":[
  5. "localhost:28010",
  6. "localhost:28012",
  7. "localhost:28011"
  1. ],
  1. "maxBsonObjectSize":16777216,
  2. "ok":1
  1. }
  1. rs1:PRIMARY>

字段说明:

  • setName:指明复制集的名称,本例中的取值是"rs1"。
  • ismaster:指明当前连接的实例是否是primary角色。它是一个布尔类型:"true"说明此实例是primary角色,"false"说明此实例不是primary角色。
  • secondary:指明当前连接的实例是否是slave角色。它是一个布尔类型:"true"说明此实例是slave角色,"false"说明此实例不是slave角色。
  • hosts:指明Replica Sets各成员IP及端口信息。
  • ok:表明复制集的状态。“1”说明状态正常,“0”说明状态异常。