MongoDB手册

  1. 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中的一些概念:

5.1 数据库

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

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

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

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

$ ./mongo

MongoDB shell version: 3.0.6

connecting to: test

> show dbs

local  0.078GB

test   0.078GB

> 

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

$ ./mongo

MongoDB shell version: 3.0.6

connecting to: test

> db

test

> 

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

> use local

switched to db local

> db

local

> 

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

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

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

不能是空字符串("")。

不得含有' '(空格)、.、$、/、\和\0 (空字符)。

应全部小写。

最多64字节。

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

admin: 从权限的角度来看,这是"root"数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。

local: 这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合

config: 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。

5.2 文档

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

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

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

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

RDBMS

MongoDB

数据库

数据库

表格

集合

文档

字段

表联合

嵌入文档

主键

主键 (MongoDB 提供了 key 为 _id )

数据库服务和客户端

Mysqld/Oracle

mongod

mysql/sqlplus

mongo

需要注意的是:

文档中的键/值对是有序的。

文档中的值不仅可以是在双引号里面的字符串,还可以是其他几种数据类型(甚至可以是整个嵌入的文档)。

MongoDB区分类型和大小写。

MongoDB的文档不能有重复的键。

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

文档键命名规范:

键不能含有\0 (空字符)。这个字符用来表示键的结尾。

.和$有特别的意义,只有在特定环境下才能使用。

以下划线"_"开头的键是保留的(不是严格要求的)。

5.3 集合

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

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

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

{"site":"www.baidu.com"}

{"site":"www.google.com","name":"Google"}

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

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

5.3.1 合法的集合名

集合名不能是空字符串""。

集合名不能含有\0字符(空字符),这个字符表示集合名的结尾。

集合名不能以"system."开头,这是为系统集合保留的前缀。

用户创建的集合名字不能含有保留字符。有些驱动程序的确支持在集合名里面包含,这是因为某些系统生成的集合中包含该字符。除非你要访问这种系统创建的集合,否则千万不要在名字里出现$。 

如下实例:

db.col.findOne()

5.3.2 capped collections

Capped collections 就是固定大小的collection。

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

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

Capped collections 可以按照文档的插入顺序保存到集合中,而且这些文档在磁盘上存放位置也是按照插入顺序来保存的,所以当我们更新Capped collections 中文档的时候,更新后的文档不可以超过之前文档的大小,这样话就可以确保所有文档在磁盘上的位置一直保持不变。

由于 Capped collection 是按照文档的插入顺序而不是使用索引确定插入位置,这样的话可以提高增添数据的效率。MongoDB 的操作日志文件 oplog.rs 就是利用 Capped Collection 来实现的。

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

db.createCollection("mycoll", {capped:true, size:100000})

在 capped collection 中,你能添加新的对象。

能进行更新,然而,对象不会增加存储空间。如果增加,更新就会失败 。

使用 Capped Collection 不能删除一个文档,可以使用 drop() 方法删除 collection 所有的行。

删除之后,你必须显式的重新创建这个 collection。

在32bit机器中,capped collection 最大存储为 1e9( 1X109)个字节。

5.4 元数据

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

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}}是可删除的。

5.5 MongoDB 数据类型

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

数据类型

描述

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

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

下面说明下几种重要的数据类型。

5.5.1 ObjectId

ObjectId 类似唯一主键,可以很快的去生成和排序,包含 12 bytes,含义是:

前 4 个字节表示创建 unix 时间戳,格林尼治时间 UTC 时间,比北京时间晚了 8 个小时

接下来的 3 个字节是机器标识码

紧接的两个字节由进程 id 组成 PID

最后三个字节是随机数

MongoDB 中存储的文档必须有一个 _id 键。这个键的值可以是任何类型的,默认是个 ObjectId 对象

由于 ObjectId 中保存了创建的时间戳,所以你不需要为你的文档保存时间戳字段,你可以通过 getTimestamp 函数来获取文档的创建时间:

> var newObject = ObjectId()

> newObject.getTimestamp()

ISODate("2017-11-25T07:21:10Z")

ObjectId 转为字符串

> newObject.str

5a1919e63df83ce79df8b38f

5.5.2 字符串

BSON 字符串都是 UTF-8 编码。

5.5.3 时间戳

BSON 有一个特殊的时间戳类型用于 MongoDB 内部使用,与普通的 日期 类型不相关。 时间戳值是一个 64 位的值。其中:

前32位是一个 time_t 值(与Unix新纪元相差的秒数)

后32位是在某秒中操作的一个递增的序数

在单个 mongod 实例中,时间戳值通常是唯一的。

在复制集中, oplog 有一个 ts 字段。这个字段中的值使用BSON时间戳表示了操作时间。

BSON 时间戳类型主要用于 MongoDB 内部使用。在大多数情况下的应用开发中,你可以使用 BSON 日期类型。

5.5.4 日期

表示当前距离 Unix新纪元(1970年1月1日)的毫秒数。日期类型是有符号的, 负数表示 1970 年之前的日期。

> var mydate1 = new Date()     //格林尼治时间

> mydate1

ISODate("2018-03-04T14:58:51.233Z")

> typeof mydate1

object

> var mydate2 = ISODate() //格林尼治时间

> mydate2

ISODate("2018-03-04T15:00:45.479Z")

> typeof mydate2

object

这样创建的时间是日期类型,可以使用 JS 中的 Date 类型的方法。

返回一个时间类型的字符串:

> var mydate1str = mydate1.toString()

> mydate1str

Sun Mar 04 2018 14:58:51 GMT+0000 (UTC)

> typeof mydate1str

string

或者

> Date()

Sun Mar 04 2018 15:02:59 GMT+0000 (UTC)  

  1. MongoDB - 连接

在本教程我们将讨论 MongoDB 的不同连接方式。

6.1 启动 MongoDB 服务

在前面的教程中,我们已经讨论了如何启动 MongoDB 服务,你只需要在 MongoDB 安装目录的 bin 目录下执行 mongodb 即可。

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

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

标准 URI 连接语法:

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

mongodb:// 这是固定的格式,必须要指定。

username:password@ 可选项,如果设置,在连接数据库服务器之后,驱动都会尝试登陆这个数据库

host1 必须的指定至少一个host, host1 是这个URI唯一要填写的。它指定了要连接服务器的地址。如果要连接复制集,请指定多个主机地址。

portX 可选的指定端口,如果不填,默认为27017

/database 如果指定username:password@,连接并验证登陆指定数据库。若不指定,默认打开 test 数据库。

?options 是连接选项。如果不使用/database,则前面需要加上/。所有连接选项都是键值对name=value,键值对之间通过&或;(分号)隔开

标准的连接格式包含了多个选项(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的时间。

6.2 实例

使用默认端口来连接 MongoDB 的服务

mongodb://localhost

通过 shell 连接 MongoDB 服务:

$ ./mongo

MongoDB shell version: 3.0.6

connecting to: test

...

这时候你返回查看运行 ./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)  # 该行表明一个来自本机的连接

……省略信息……

6.3 MongoDB 连接命令格式

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

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

$ ./mongo

MongoDB shell version: 3.0.6

connecting to: test

使用用户 admin 使用密码 123456 连接到本地的 MongoDB 服务上。输出结果如下所示:

> mongodb://admin:123456@localhost/

...

使用用户名和密码连接登陆到指定数据库,格式如下:

mongodb://admin:123456@localhost/test

6.3.1 更多连接实例

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

mongodb://localhost

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

mongodb://fred:foobar@localhost

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

mongodb://fred:foobar@localhost/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

  1. MongoDB 创建数据库

7.1 语法

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

use DATABASE_NAME

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

实例

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

> use runoob

switched to db runoob

> db

runoob

>

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

> show dbs

admin   0.000GB

local   0.000GB

>

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

> db.runoob.insert({"name":"菜鸟教程"})

WriteResult({ "nInserted" : 1 })

> show dbs

local   0.078GB

runoob  0.078GB

test    0.078GB

>

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

注意: 在 MongoDB 中,集合只有在内容插入后才会创建! 就是说,创建集合(数据表)后要再插入一个文档(记录),集合才会真正创建。

  1. MongoDB 删除数据库

8.1 语法

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

db.dropDatabase()

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

8.2 实例

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

首先,查看所有数据库:

> show dbs

local   0.078GB

runoob  0.078GB

test    0.078GB

接下来我们切换到数据库 runoob:

> use runoob

switched to db runoob

>

执行删除命令:

> db.dropDatabase()

{ "dropped" : "runoob", "ok" : 1 }

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

> show dbs

local  0.078GB

test   0.078GB

>

8.3 删除集合

集合删除语法格式如下:

db.collection.drop()

以下实例删除了 runoob 数据库中的集合 site:

> use runoob

switched to db runoob

> show tables

site

> db.site.drop()

true

> show tables

>

9. MongoDB 创建集合

本章节我们为大家介绍如何使用 MongoDB 来创建集合。

MongoDB 中使用 createCollection() 方法来创建集合。

语法格式:

db.createCollection(name, options)

参数说明:

name: 要创建的集合名称

options: 可选参数, 指定有关内存大小及索引的选项

options 可以是如下参数:

字段

类型

描述

capped

布尔

(可选)如果为 true,则创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。
当该值为 true 时,必须指定 size 参数。

autoIndexId

布尔

(可选)如为 true,自动在 _id 字段创建索引。默认为 false。

size

数值

(可选)为固定集合指定一个最大值(以字节计)。
如果 capped 为 true,也需要指定该字段。

max

数值

(可选)指定固定集合中包含文档的最大数量。

在插入文档时,MongoDB 首先检查固定集合的 size 字段,然后检查 max 字段。

实例

在 test 数据库中创建 runoob 集合:

> use test

switched to db test

> db.createCollection("runoob")

{ "ok" : 1 }

>

如果要查看已有集合,可以使用 show collections 命令:

> show collections

runoob

system.indexes

下面是带有几个关键参数的 createCollection() 的用法:

创建固定集合 mycol,整个集合空间大小 6142800 KB, 文档最大个数为 10000 个。

> db.createCollection("mycol", { capped : true, autoIndexId : true, size :

   6142800, max : 10000 } )

{ "ok" : 1 }

>

在 MongoDB 中,你不需要创建集合。当你插入一些文档时,MongoDB 会自动创建集合。

> db.mycol2.insert({"name" : "菜鸟教程"})

> show collections

mycol2

...

10. MongoDB 删除集合

本章节我们为大家介绍如何使用 MongoDB 来删除集合。

MongoDB 中使用 drop() 方法来删除集合。

语法格式:

db.collection.drop()

参数说明:

返回值

如果成功删除选定集合,则 drop() 方法返回 true,否则返回 false。

实例

在数据库 mydb 中,我们可以先通过 show collections 命令查看已存在的集合:

>use mydb

switched to db mydb

>show collections

mycol

mycol2

system.indexes

runoob

>

接着删除集合 mycol2 :

>db.mycol2.drop()

true

>

通过 show collections 再次查看数据库 mydb 中的集合:

>show collections

mycol

system.indexes

runoob

>

从结果中可以看出 mycol2 集合已被删除。

11. MongoDB 插入文档

本章节中我们将向大家介绍如何将数据插入到MongoDB的集合中。

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

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

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

11.1 插入文档

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

db.COLLECTION_NAME.insert(document)

实例

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

>db.col.insert({title: 'MongoDB 教程',

    description: 'MongoDB 是一个 Nosql 数据库',

    by: '菜鸟教程',

    url: 'http://www.runoob.com',

    tags: ['mongodb', 'database', 'NoSQL'],

    likes: 100

})

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

查看已插入文档:

> db.col.find()

{ "_id" : ObjectId("56064886ade2f21f36b03134"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 100 }

>

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

> document=({title: 'MongoDB 教程',

    description: 'MongoDB 是一个 Nosql 数据库',

    by: '菜鸟教程',

    url: 'http://www.runoob.com',

    tags: ['mongodb', 'database', 'NoSQL'],

    likes: 100

});

执行后显示结果如下:

{

        "title" : "MongoDB 教程",

        "description" : "MongoDB 是一个 Nosql 数据库",

        "by" : "菜鸟教程",

        "url" : "http://www.runoob.com",

        "tags" : [

                "mongodb",

                "database",

                "NoSQL"

        ],

        "likes" : 100

}

执行插入操作:

> db.col.insert(document)

WriteResult({ "nInserted" : 1 })

>

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

12. MongoDB 更新文档

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

12.1 update() 方法

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

db.collection.update(

   <query>,

   <update>,

   {

     upsert: <boolean>,

     multi: <boolean>,

     writeConcern: <document>

   }

)

参数说明:

query : update的查询条件,类似sql update查询内where后面的。

update : update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql update查询内set后面的

upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。

multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。

writeConcern :可选,抛出异常的级别。

实例

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

>db.col.insert({

    title: 'MongoDB 教程',

    description: 'MongoDB 是一个 Nosql 数据库',

    by: '菜鸟教程',

    url: 'http://www.runoob.com',

    tags: ['mongodb', 'database', 'NoSQL'],

    likes: 100

})

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

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

WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })   # 输出信息

> db.col.find().pretty()

{

        "_id" : ObjectId("56064f89ade2f21f36b03136"),

        "title" : "MongoDB",

        "description" : "MongoDB 是一个 Nosql 数据库",

        "by" : "菜鸟教程",

        "url" : "http://www.runoob.com",

        "tags" : [

                "mongodb",

                "database",

                "NoSQL"

        ],

        "likes" : 100

}

>

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

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

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


12.2 save() 方法

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

db.collection.save(

   <document>,

   {

     writeConcern: <document>

   }

)

参数说明:

document : 文档数据。

writeConcern :可选,抛出异常的级别。

实例

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

>db.col.save({

    "_id" : ObjectId("56064f89ade2f21f36b03136"),

    "title" : "MongoDB",

    "description" : "MongoDB 是一个 Nosql 数据库",

    "by" : "Runoob",

    "url" : "http://www.runoob.com",

    "tags" : [

            "mongodb",

            "NoSQL"

    ],

    "likes" : 110

})

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

>db.col.find().pretty()

{

        "_id" : ObjectId("56064f89ade2f21f36b03136"),

        "title" : "MongoDB",

        "description" : "MongoDB 是一个 Nosql 数据库",

        "by" : "Runoob",

        "url" : "http://www.runoob.com",

        "tags" : [

                "mongodb",

                "NoSQL"

        ],

        "likes" : 110

}

>

12.3 更多实例

只更新第一条记录:

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

全部更新:

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

只添加第一条:

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

全部添加加进去:

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

全部更新:

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

只更新第一条记录:

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

13. MongoDB 删除文档

在前面的几个章节中我们已经学习了MongoDB中如何为集合添加数据和更新数据。在本章节中我们将继续学习MongoDB集合的删除。

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

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

语法

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

db.collection.remove(

   <query>,

   <justOne>

)

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

db.collection.remove(

   <query>,

   {

     justOne: <boolean>,

     writeConcern: <document>

   }

)

参数说明:

query :(可选)删除的文档的条件。

justOne : (可选)如果设为 true 或 1,则只删除一个文档。

writeConcern :(可选)抛出异常的级别。

实例

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

>db.col.insert({title: 'MongoDB 教程',

    description: 'MongoDB 是一个 Nosql 数据库',

    by: '菜鸟教程',

    url: 'http://www.runoob.com',

    tags: ['mongodb', 'database', 'NoSQL'],

    likes: 100

})

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

> db.col.find()

{ "_id" : ObjectId("56066169ade2f21f36b03137"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 100 }

{ "_id" : ObjectId("5606616dade2f21f36b03138"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 100 }

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

>db.col.remove({'title':'MongoDB 教程'})

WriteResult({ "nRemoved" : 2 })           # 删除了两条数据

>db.col.find()

……                                        # 没有数据

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

>db.COLLECTION_NAME.remove(DELETION_CRITERIA,1)

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

>db.col.remove({})

>db.col.find()

>

14. MongoDB 查询文档

MongoDB 查询文档使用 find() 方法。

find() 方法以非结构化的方式来显示所有文档。

14.1 语法

MongoDB 查询数据的语法格式如下:

db.collection.find(query, projection)

query :可选,使用查询操作符指定查询条件

projection :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。

如果你需要以易读的方式来读取数据,可以使用 pretty() 方法,语法格式如下:

>db.col.find().pretty()

pretty() 方法以格式化的方式来显示所有文档。

14.2 实例

以下实例我们查询了集合 col 中的数据:

> db.col.find().pretty()

{

        "_id" : ObjectId("56063f17ade2f21f36b03133"),

        "title" : "MongoDB 教程",

        "description" : "MongoDB 是一个 Nosql 数据库",

        "by" : "菜鸟教程",

        "url" : "http://www.runoob.com",

        "tags" : [

                "mongodb",

                "database",

                "NoSQL"

        ],

        "likes" : 100

}

除了 find() 方法之外,还有一个 findOne() 方法,它只返回一个文档。

14.3 MongoDB 与 RDBMS Where 语句比较

如果你熟悉常规的 SQL 数据,通过下表可以更好的理解 MongoDB 的条件语句查询:

操作

格式

范例

RDBMS中的类似语句

等于

{<key>:<value>}

db.col.find({"by":"菜鸟教程"}).pretty()

where by = '菜鸟教程'

小于

{<key>:{$lt:<value>}}

db.col.find({"likes":{$lt:50}}).pretty()

where likes < 50

小于或等于

{<key>:{$lte:<value>}}

db.col.find({"likes":{$lte:50}}).pretty()

where likes <= 50

大于

{<key>:{$gt:<value>}}

db.col.find({"likes":{$gt:50}}).pretty()

where likes > 50

大于或等于

{<key>:{$gte:<value>}}

db.col.find({"likes":{$gte:50}}).pretty()

where likes >= 50

不等于

{<key>:{$ne:<value>}}

db.col.find({"likes":{$ne:50}}).pretty()

where likes != 50

14.4 MongoDB AND 条件

MongoDB 的 find() 方法可以传入多个键(key),每个键(key)以逗号隔开,即常规 SQL 的 AND 条件。

语法格式如下:

>db.col.find({key1:value1, key2:value2}).pretty()

实例

以下实例通过 by 和 title 键来查询 菜鸟教程 中 MongoDB 教程 的数据

> db.col.find({"by":"菜鸟教程", "title":"MongoDB 教程"}).pretty()

{

        "_id" : ObjectId("56063f17ade2f21f36b03133"),

        "title" : "MongoDB 教程",

        "description" : "MongoDB 是一个 Nosql 数据库",

        "by" : "菜鸟教程",

        "url" : "http://www.runoob.com",

        "tags" : [

                "mongodb",

                "database",

                "NoSQL"

        ],

        "likes" : 100

}

以上实例中类似于 WHERE 语句:WHERE by='菜鸟教程' AND title='MongoDB 教程'

14.5 MongoDB OR 条件

MongoDB OR 条件语句使用了关键字 $or,语法格式如下:

>db.col.find(

   {

      $or: [

         {key1: value1}, {key2:value2}

      ]

   }

).pretty()

实例

以下实例中,我们演示了查询键 by 值为 菜鸟教程 或键 title 值为 MongoDB 教程 的文档。

>db.col.find({$or:[{"by":"菜鸟教程"},{"title": "MongoDB 教程"}]}).pretty()

{

        "_id" : ObjectId("56063f17ade2f21f36b03133"),

        "title" : "MongoDB 教程",

        "description" : "MongoDB 是一个 Nosql 数据库",

        "by" : "菜鸟教程",

        "url" : "http://www.runoob.com",

        "tags" : [

                "mongodb",

                "database",

                "NoSQL"

        ],

        "likes" : 100

}

>

14.6 AND 和 OR 联合使用

以下实例演示了 AND 和 OR 联合使用,类似常规 SQL 语句为: 'where likes>50 AND (by = '菜鸟教程' OR title = 'MongoDB 教程')'

>db.col.find({"likes": {$gt:50}, $or: [{"by": "菜鸟教程"},{"title": "MongoDB 教程"}]}).pretty()

{

        "_id" : ObjectId("56063f17ade2f21f36b03133"),

        "title" : "MongoDB 教程",

        "description" : "MongoDB 是一个 Nosql 数据库",

        "by" : "菜鸟教程",

        "url" : "http://www.runoob.com",

        "tags" : [

                "mongodb",

                "database",

                "NoSQL"

        ],

        "likes" : 100

}

15. MongoDB 条件操作符

15.1 描述

条件操作符用于比较两个表达式并从mongoDB集合中获取数据。

在本章节中,我们将讨论如何在MongoDB中使用条件操作符。

MongoDB中条件操作符有:

(>) 大于 - $gt

(<) 小于 - $lt

(>=) 大于等于 - $gte

(<= ) 小于等于 - $lte

我们使用的数据库名称为"runoob" 我们的集合名称为"col",以下为我们插入的数据。

为了方便测试,我们可以先使用以下命令清空集合 "col" 的数据:

db.col.remove({})

插入以下数据

>db.col.insert({

    title: 'PHP 教程',

    description: 'PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。',

    by: '菜鸟教程',

    url: 'http://www.runoob.com',

    tags: ['php'],

    likes: 200

})

>db.col.insert({title: 'Java 教程',

    description: 'Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。',

    by: '菜鸟教程',

    url: 'http://www.runoob.com',

    tags: ['java'],

    likes: 150

})

>db.col.insert({title: 'MongoDB 教程',

    description: 'MongoDB 是一个 Nosql 数据库',

    by: '菜鸟教程',

    url: 'http://www.runoob.com',

    tags: ['mongodb'],

    likes: 100

})

使用find()命令查看数据:

> db.col.find()

{ "_id" : ObjectId("56066542ade2f21f36b0313a"), "title" : "PHP 教程", "description" : "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "php" ], "likes" : 200 }

{ "_id" : ObjectId("56066549ade2f21f36b0313b"), "title" : "Java 教程", "description" : "Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "java" ], "likes" : 150 }

{ "_id" : ObjectId("5606654fade2f21f36b0313c"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb" ], "likes" : 100 }

15.2 MongoDB (>) 大于操作符 - $gt

如果你想获取 "col" 集合中 "likes" 大于 100 的数据,你可以使用以下命令:

db.col.find({likes : {$gt : 100}})

类似于SQL语句:

Select * from col where likes > 100;

输出结果:

> db.col.find({likes : {$gt : 100}})

{ "_id" : ObjectId("56066542ade2f21f36b0313a"), "title" : "PHP 教程", "description" : "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "php" ], "likes" : 200 }

{ "_id" : ObjectId("56066549ade2f21f36b0313b"), "title" : "Java 教程", "description" : "Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "java" ], "likes" : 150 }

>


15.3 MongoDB(>=)大于等于操作符 - $gte

如果你想获取"col"集合中 "likes" 大于等于 100 的数据,你可以使用以下命令:

db.col.find({likes : {$gte : 100}})

类似于SQL语句:

Select * from col where likes >=100;

输出结果:

> db.col.find({likes : {$gte : 100}})

{ "_id" : ObjectId("56066542ade2f21f36b0313a"), "title" : "PHP 教程", "description" : "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "php" ], "likes" : 200 }

{ "_id" : ObjectId("56066549ade2f21f36b0313b"), "title" : "Java 教程", "description" : "Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "java" ], "likes" : 150 }

{ "_id" : ObjectId("5606654fade2f21f36b0313c"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb" ], "likes" : 100 }

>

15.4 MongoDB (<) 小于操作符 - $lt

如果你想获取"col"集合中 "likes" 小于 150 的数据,你可以使用以下命令:

db.col.find({likes : {$lt : 150}})

类似于SQL语句:

Select * from col where likes < 150;

输出结果:

> db.col.find({likes : {$lt : 150}})

{ "_id" : ObjectId("5606654fade2f21f36b0313c"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb" ], "likes" : 100 }

15.5 MongoDB (<=) 小于操作符 - $lte

如果你想获取"col"集合中 "likes" 小于等于 150 的数据,你可以使用以下命令:

db.col.find({likes : {$lte : 150}})

类似于SQL语句:

Select * from col where likes <= 150;

输出结果:

> db.col.find({likes : {$lte : 150}})

{ "_id" : ObjectId("56066549ade2f21f36b0313b"), "title" : "Java 教程", "description" : "Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "java" ], "likes" : 150 }

{ "_id" : ObjectId("5606654fade2f21f36b0313c"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb" ], "likes" : 100 }

15.6 MongoDB使用(<)和(>)查询 - $lt 和 $gt

如果你想获取"col"集合中 "likes" 大于100,小于 200 的数据,你可以使用以下命令:

db.col.find({likes : {$lt :200, $gt : 100}})

类似于SQL语句:

Select * from col where likes>100 AND  likes<200;

输出结果

> db.col.find({likes : {$lt :200, $gt : 100}})

{ "_id" : ObjectId("56066549ade2f21f36b0313b"), "title" : "Java 教程", "description" : "Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "java" ], "likes" : 150 }

>

16. MongoDB $type 操作符

16.1 描述

在本章节中,我们将继续讨论MongoDB中条件操作符 $type。

$type操作符是基于BSON类型来检索集合中匹配的数据类型,并返回结果。

MongoDB 中可以使用的类型如下表所示:

类型

数字

备注

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

我们使用的数据库名称为"runoob" 我们的集合名称为"col",以下为我们插入的数据。

简单的集合"col":

>db.col.insert({

    title: 'PHP 教程',

    description: 'PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。',

    by: '菜鸟教程',

    url: 'http://www.runoob.com',

    tags: ['php'],

    likes: 200

})

>db.col.insert({title: 'Java 教程',

    description: 'Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。',

    by: '菜鸟教程',

    url: 'http://www.runoob.com',

    tags: ['java'],

    likes: 150

})

>db.col.insert({title: 'MongoDB 教程',

    description: 'MongoDB 是一个 Nosql 数据库',

    by: '菜鸟教程',

    url: 'http://www.runoob.com',

    tags: ['mongodb'],

    likes: 100

})

使用find()命令查看数据:

> db.col.find()

{ "_id" : ObjectId("56066542ade2f21f36b0313a"), "title" : "PHP 教程", "description" : "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "php" ], "likes" : 200 }

{ "_id" : ObjectId("56066549ade2f21f36b0313b"), "title" : "Java 教程", "description" : "Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "java" ], "likes" : 150 }

{ "_id" : ObjectId("5606654fade2f21f36b0313c"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb" ], "likes" : 100 }

16.2 MongoDB 操作符 - $type 实例

如果想获取 "col" 集合中 title 为 String 的数据,你可以使用以下命令:

db.col.find({"title" : {$type : 2}})

db.col.find({"title" : {$type : 'string'}})

输出结果为:

{ "_id" : ObjectId("56066542ade2f21f36b0313a"), "title" : "PHP 教程", "description" : "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "php" ], "likes" : 200 }

{ "_id" : ObjectId("56066549ade2f21f36b0313b"), "title" : "Java 教程", "description" : "Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "java" ], "likes" : 150 }

{ "_id" : ObjectId("5606654fade2f21f36b0313c"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb" ], "likes" : 100 }

17. MongoDB Limit与Skip方法

17.1 MongoDB Limit() 方法

如果你需要在MongoDB中读取指定数量的数据记录,可以使用MongoDB的Limit方法,limit()方法接受一个数字参数,该参数指定从MongoDB中读取的记录条数。

语法

limit()方法基本语法如下所示:

>db.COLLECTION_NAME.find().limit(NUMBER)

实例

集合 col 中的数据如下:

{ "_id" : ObjectId("56066542ade2f21f36b0313a"), "title" : "PHP 教程", "description" : "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "php" ], "likes" : 200 }

{ "_id" : ObjectId("56066549ade2f21f36b0313b"), "title" : "Java 教程", "description" : "Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "java" ], "likes" : 150 }

{ "_id" : ObjectId("5606654fade2f21f36b0313c"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb" ], "likes" : 100 }

以下实例为显示查询文档中的两条记录:

> db.col.find({},{"title":1,_id:0}).limit(2)

{ "title" : "PHP 教程" }

{ "title" : "Java 教程" }

>

注:如果你们没有指定limit()方法中的参数则显示集合中的所有数据。

17.2 MongoDB Skip() 方法

我们除了可以使用limit()方法来读取指定数量的数据外,还可以使用skip()方法来跳过指定数量的数据,skip方法同样接受一个数字参数作为跳过的记录条数。

语法

skip() 方法脚本语法格式如下:

>db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER)

实例

以下实例只会显示第二条文档数据

>db.col.find({},{"title":1,_id:0}).limit(1).skip(1)

{ "title" : "Java 教程" }

>

注:skip()方法默认参数为 0 。

18. MongoDB 排序

18.1 MongoDB sort() 方法

在 MongoDB 中使用 sort() 方法对数据进行排序,sort() 方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列。

语法

sort()方法基本语法如下所示:

>db.COLLECTION_NAME.find().sort({KEY:1})

实例

col 集合中的数据如下:

{ "_id" : ObjectId("56066542ade2f21f36b0313a"), "title" : "PHP 教程", "description" : "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "php" ], "likes" : 200 }

{ "_id" : ObjectId("56066549ade2f21f36b0313b"), "title" : "Java 教程", "description" : "Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "java" ], "likes" : 150 }

{ "_id" : ObjectId("5606654fade2f21f36b0313c"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb" ], "likes" : 100 }

以下实例演示了 col 集合中的数据按字段 likes 的降序排列:

>db.col.find({},{"title":1,_id:0}).sort({"likes":-1})

{ "title" : "PHP 教程" }

{ "title" : "Java 教程" }

{ "title" : "MongoDB 教程" }

>

19. MongoDB 索引

索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。

这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。

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

19.1 createIndex() 方法

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

注意在 3.0.0 版本前创建索引方法为 db.collection.ensureIndex(),之后的版本使用了 db.collection.createIndex() 方法,ensureIndex() 还能用,但只是 createIndex() 的别名。

语法

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

>db.collection.createIndex(keys, options)

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

实例

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

>

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

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

>

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

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.

实例

在后台创建索引:

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

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

20. MongoDB 聚合

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

20.1 aggregate() 方法

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

语法

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

>db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)

实例

集合中的数据如下:

{

   _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

},

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

> 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语句:

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

20.2 管道的概念

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

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

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

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

$project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。

$match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。

$limit:用来限制MongoDB聚合管道返回的文档数。

$skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。

$unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。

$group:将集合中的文档分组,可用于统计结果。

$sort:将输入文档排序后输出。

$geoNear:输出接近某一地理位置的有序文档。

20.2.1 管道操作符实例

1、$project实例

db.article.aggregate(

    { $project : {

        title : 1 ,

        author : 1 ,

    }}

 );

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

db.article.aggregate(

    { $project : {

        _id : 0 ,

        title : 1 ,

        author : 1

    }});

2.$match实例

db.articles.aggregate( [

                        { $match : { score : { $gt : 70, $lte : 90 } } },

                        { $group: { _id: null, count: { $sum: 1 } } }

                       ] );

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

3.$skip实例

db.article.aggregate(

    { $skip : 5 });

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

21. MongoDB 复制(副本集)

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

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

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

21.1 什么是复制?

保障数据的安全性

数据高可用性 (24*7)

灾难恢复

无需停机维护(如备份,重建索引,压缩)

分布式读取数据

20.2 MongoDB复制原理

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

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

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

MongoDB复制结构图如下所示:

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

副本集特征:

N 个节点的集群

任何节点可作为主节点

所有写入操作都在主节点上

自动故障转移

自动恢复

20.3 MongoDB副本集设置

在本教程中我们使用同一个MongoDB来做MongoDB主从的实验, 操作步骤如下:

1、关闭正在运行的MongoDB服务器。

现在我们通过指定 --replSet 选项来启动mongoDB。--replSet 基本语法格式如下:

mongod --port "PORT" --dbpath "YOUR_DB_DATA_PATH" --replSet "REPLICA_SET_INSTANCE_NAME"

实例

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

以上实例会启动一个名为rs0的MongoDB实例,其端口号为27017。

启动后打开命令提示框并连接上mongoDB服务。

在Mongo客户端使用命令rs.initiate()来启动一个新的副本集。

我们可以使用rs.conf()来查看副本集的配置

查看副本集状态使用 rs.status() 命令

20.4 副本集添加成员

添加副本集的成员,我们需要使用多台服务器来启动mongo服务。进入Mongo客户端,并使用rs.add()方法来添加副本集的成员。

语法

rs.add() 命令基本语法格式如下:

>rs.add(HOST_NAME:PORT)

实例

假设你已经启动了一个名为mongod1.net,端口号为27017的Mongo服务。 在客户端命令窗口使用rs.add() 命令将其添加到副本集中,命令如下所示:

>rs.add("mongod1.net:27017")

>

MongoDB中你只能通过主节点将Mongo服务添加到副本集中, 判断当前运行的Mongo服务是否为主节点可以使用命令db.isMaster() 。

MongoDB的副本集与我们常见的主从有所不同,主从在主机宕机后所有服务将停止,而副本集在主机宕机后,副本会接管主节点成为主节点,不会出现宕机的情况。

21. MongoDB 分片

21.1 分片

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

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

21.2 为什么使用分片

复制所有的写入操作到主节点

延迟的敏感数据会在主节点查询

单个副本集限制在12个节点

当请求量巨大时会出现内存不足。

本地磁盘不足

垂直扩展价格昂贵

21.3 MongoDB分片

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

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

Shard:

用于存储实际的数据块,实际生产环境中一个shard server角色可由几台机器组个一个replica set承担,防止主机单点故障

Config Server:

mongod实例,存储了整个 ClusterMetadata,其中包括 chunk信息。

Query Routers:

前端路由,客户端由此接入,且让整个集群看上去像单一数据库,前端应用可以透明使用。

21.4 分片实例

分片结构端口分布如下:

Shard Server 1:27020

Shard Server 2:27021

Shard Server 3:27022

Shard Server 4:27023

Config Server :27100

Route Process:40000

步骤一:启动Shard Server

[root@100 /]# mkdir -p /www/mongoDB/shard/s0

[root@100 /]# mkdir -p /www/mongoDB/shard/s1

[root@100 /]# mkdir -p /www/mongoDB/shard/s2

[root@100 /]# mkdir -p /www/mongoDB/shard/s3

[root@100 /]# mkdir -p /www/mongoDB/shard/log

[root@100 /]# /usr/local/mongoDB/bin/mongod --port 27020 --dbpath=/www/mongoDB/shard/s0 --logpath=/www/mongoDB/shard/log/s0.log --logappend --fork

....

[root@100 /]# /usr/local/mongoDB/bin/mongod --port 27023 --dbpath=/www/mongoDB/shard/s3 --logpath=/www/mongoDB/shard/log/s3.log --logappend --fork

步骤二: 启动Config Server

[root@100 /]# mkdir -p /www/mongoDB/shard/config

[root@100 /]# /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

/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节点

[root@100 shard]# /usr/local/mongoDB/bin/mongo admin --port 40000

MongoDB shell version: 2.0.7

connecting to: 127.0.0.1:40000/admin

mongos> db.runCommand({ addshard:"localhost:27020" })

{ "shardAdded" : "shard0000", "ok" : 1 }

......

mongos> db.runCommand({ addshard:"localhost:27029" })

{ "shardAdded" : "shard0009", "ok" : 1 }

mongos> db.runCommand({ enablesharding:"test" }) #设置分片存储的数据库

{ "ok" : 1 }

mongos> db.runCommand({ shardcollection: "test.log", key: { id:1,time:1}})

{ "collectionsharded" : "test.log", "ok" : 1 }

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

22. MongoDB 备份(mongodump)与恢复(mongorestore)

22.1 MongoDB数据备份

在Mongodb中我们使用mongodump命令来备份MongoDB数据。该命令可以导出所有数据到指定目录中。

mongodump命令可以通过参数指定导出的数据量级转存的服务器。

语法

mongodump命令脚本语法如下:

>mongodump -h dbhost -d dbname -o dbdirectory

-h:

MongDB所在服务器地址,例如:127.0.0.1,当然也可以指定端口号:127.0.0.1:27017

-d:

需要备份的数据库实例,例如:test

-o:

备份的数据存放位置,例如:c:\data\dump,当然该目录需要提前建立,在备份完成后,系统自动在dump目录下建立一个test目录,这个目录里面存放该数据库实例的备份数据。

实例

在本地使用 27017 启动你的mongod服务。打开命令提示符窗口,进入MongoDB安装目录的bin目录输入命令mongodump:

>mongodump

执行以上命令后,客户端会连接到ip为 127.0.0.1 端口号为 27017 的MongoDB服务上,并备份所有数据到 bin/dump/ 目录中。命令输出结果如下:

mongodump 命令可选参数列表如下所示:

语法

描述

实例

mongodump --host HOST_NAME --port PORT_NUMBER

该命令将备份所有MongoDB数据

mongodump --host runoob.com --port 27017

mongodump --dbpath DB_PATH --out BACKUP_DIRECTORY

mongodump --dbpath /data/db/ --out /data/backup/

mongodump --collection COLLECTION --db DB_NAME

该命令将备份指定数据库的集合。

mongodump --collection mycol --db test

22.2 MongoDB数据恢复

mongodb使用 mongorestore 命令来恢复备份的数据。

语法

mongorestore命令脚本语法如下:

>mongorestore -h <hostname><:port> -d dbname <path>

--host <:port>, -h <:port>:

MongoDB所在服务器地址,默认为: localhost:27017

--db , -d :

需要恢复的数据库实例,例如:test,当然这个名称也可以和备份时候的不一样,比如test2

--drop:

恢复的时候,先删除当前数据,然后恢复备份的数据。就是说,恢复后,备份后添加修改的数据都会被删除,慎用哦!

<path>:

mongorestore 最后的一个参数,设置备份数据所在位置,例如:c:\data\dump\test。

你不能同时指定 <path> 和 --dir 选项,--dir也可以设置备份目录。

--dir:

指定备份的目录

你不能同时指定 <path> 和 --dir 选项。

接下来我们执行以下命令:

>mongorestore

执行以上命令输出结果如下:

23. MongoDB 监控

在你已经安装部署并允许MongoDB服务后,你必须要了解MongoDB的运行情况,并查看MongoDB的性能。这样在大流量得情况下可以很好的应对并保证MongoDB正常运作。

MongoDB中提供了mongostat 和 mongotop 两个命令来监控MongoDB的运行情况。

23.1 mongostat 命令

mongostat是mongodb自带的状态检测工具,在命令行下使用。它会间隔固定时间获取mongodb的当前运行状态,并输出。如果你发现数据库突然变慢或者有其他问题的话,你第一手的操作就考虑采用mongostat来查看mongo的状态。

启动你的Mongod服务,进入到你安装的MongoDB目录下的bin目录, 然后输入mongostat命令,如下所示:

D:\set up\mongodb\bin>mongostat

以上命令输出结果如下:

23.2 mongotop 命令

mongotop也是mongodb下的一个内置工具,mongotop提供了一个方法,用来跟踪一个MongoDB的实例,查看哪些大量的时间花费在读取和写入数据。 mongotop提供每个集合的水平的统计数据。默认情况下,mongotop返回值的每一秒。

启动你的Mongod服务,进入到你安装的MongoDB目录下的bin目录, 然后输入mongotop命令,如下所示:

D:\set up\mongodb\bin>mongotop

以上命令执行输出结果如下:

带参数实例

 E:\mongodb-win32-x86_64-2.2.1\bin>mongotop 10

后面的10是<sleeptime>参数 ,可以不使用,等待的时间长度,以秒为单位,mongotop等待调用之间。通过的默认mongotop返回数据的每一秒。

 E:\mongodb-win32-x86_64-2.2.1\bin>mongotop --locks

报告每个数据库的锁的使用中,使用mongotop - 锁,这将产生以下输出:

输出结果字段说明:

ns:

包含数据库命名空间,后者结合了数据库名称和集合。

db:

包含数据库的名称。名为 . 的数据库针对全局锁定,而非特定数据库。

total:

mongod花费的时间工作在这个命名空间提供总额。

read:

提供了大量的时间,这mongod花费在执行读操作,在此命名空间。

write:

提供这个命名空间进行写操作,这mongod花了大量的时间。

24. MongoDB Java

环境配置

在 Java 程序中如果要使用 MongoDB,你需要确保已经安装了 Java 环境及 MongoDB JDBC 驱动。

本章节实例时候 Mongo 3.x 以上版本。

你可以参考本站的Java教程来安装Java程序。现在让我们来检测你是否安装了 MongoDB JDBC 驱动。

首先你必须下载mongo jar包,下载地址:http://mongodb.github.io/mongo-java-driver/, 请确保下载最新版本。

你需要将 mongo-java-driver-3.2.2.jar (找到合适的版本)包含在你的 classpath 中。。

国内 mongodb-driver jar 下载地址:http://central.maven.org/maven2/org/mongodb/mongo-java-driver/

24.1 连接数据库

连接数据库,你需要指定数据库名称,如果指定的数据库不存在,mongo会自动创建数据库。

连接数据库的Java代码如下:

import com.mongodb.MongoClient;

import com.mongodb.client.MongoDatabase;

public class MongoDBJDBC{

   public static void main( String args[] ){

      try{   

       // 连接到 mongodb 服务

         MongoClient mongoClient = new MongoClient( "localhost" , 27017 );

       

         // 连接到数据库

         MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");  

       System.out.println("Connect to database successfully");

        

      }catch(Exception e){

        System.err.println( e.getClass().getName() + ": " + e.getMessage() );

     }

   }

}

现在,让我们来编译运行程序并连接到数据库 mycol。

你可以根据你的实际环境改变 MongoDB JDBC 驱动的路径。

本实例将 MongoDB JDBC 启动包 mongo-java-driver-3.2.2.jar 放在本地目录下:

$ javac -cp .:mongo-java-driver-3.2.2.jar MongoDBJDBC.java

$ java -cp .:mongo-java-driver-3.2.2.jar MongoDBJDBC

Connect to database successfully

Authentication: true

本实例中 Mongo 数据库无需用户名密码验证。如果你的 Mongo 需要验证用户名及密码,可以使用以下代码:

import java.util.ArrayList;  

import java.util.List;  

import com.mongodb.MongoClient;  

import com.mongodb.MongoCredential;  

import com.mongodb.ServerAddress;  

import com.mongodb.client.MongoDatabase;  

  

public class MongoDBJDBC {  

    public static void main(String[] args){  

        try {  

            //连接到MongoDB服务 如果是远程连接可以替换“localhost”为服务器所在IP地址  

            //ServerAddress()两个参数分别为 服务器地址 和 端口  

            ServerAddress serverAddress = new ServerAddress("localhost",27017);  

            List<ServerAddress> addrs = new ArrayList<ServerAddress>();  

            addrs.add(serverAddress);  

              

            //MongoCredential.createScramSha1Credential()三个参数分别为 用户名 数据库名称 密码  

            MongoCredential credential = MongoCredential.createScramSha1Credential("username", "databaseName", "password".toCharArray());  

            List<MongoCredential> credentials = new ArrayList<MongoCredential>();  

            credentials.add(credential);  

              

            //通过连接认证获取MongoDB连接  

            MongoClient mongoClient = new MongoClient(addrs,credentials);  

              

            //连接到数据库  

            MongoDatabase mongoDatabase = mongoClient.getDatabase("databaseName");  

            System.out.println("Connect to database successfully");  

        } catch (Exception e) {  

            System.err.println( e.getClass().getName() + ": " + e.getMessage() );  

        }  

    }  

}

24.2 创建集合

我们可以使用 com.mongodb.client.MongoDatabase 类中的createCollection()来创建集合

代码片段如下:

import com.mongodb.MongoClient;

import com.mongodb.client.MongoDatabase;

public class MongoDBJDBC{

   public static void main( String args[] ){

      try{   

      // 连接到 mongodb 服务

      MongoClient mongoClient = new MongoClient( "localhost" , 27017 );

         

       

      // 连接到数据库

      MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");  

      System.out.println("Connect to database successfully");

      mongoDatabase.createCollection("test");

      System.out.println("集合创建成功");

        

      }catch(Exception e){

        System.err.println( e.getClass().getName() + ": " + e.getMessage() );

     }

   }

}

编译运行以上程序,输出结果如下:

Connect to database successfully

集合创建成功

24.3 获取集合

我们可以使用com.mongodb.client.MongoDatabase类的 getCollection() 方法来获取一个集合

代码片段如下:

import org.bson.Document;

import com.mongodb.MongoClient;

import com.mongodb.client.MongoCollection;

import com.mongodb.client.MongoDatabase;

public class MongoDBJDBC{

   public static void main( String args[] ){

      try{   

       // 连接到 mongodb 服务

         MongoClient mongoClient = new MongoClient( "localhost" , 27017 );

       

         // 连接到数据库

         MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");  

       System.out.println("Connect to database successfully");

      

       MongoCollection<Document> collection = mongoDatabase.getCollection("test");

       System.out.println("集合 test 选择成功");

      }catch(Exception e){

        System.err.println( e.getClass().getName() + ": " + e.getMessage() );

     }

   }

}

编译运行以上程序,输出结果如下:

Connect to database successfully

集合 test 选择成功

24.4 插入文档

我们可以使用com.mongodb.client.MongoCollection类的 insertMany() 方法来插入一个文档

代码片段如下:

import java.util.ArrayList;

import java.util.List;

import org.bson.Document;

import com.mongodb.MongoClient;

import com.mongodb.client.MongoCollection;

import com.mongodb.client.MongoDatabase;

public class MongoDBJDBC{

   public static void main( String args[] ){

      try{   

         // 连接到 mongodb 服务

         MongoClient mongoClient = new MongoClient( "localhost" , 27017 );

         

         // 连接到数据库

         MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");  

         System.out.println("Connect to database successfully");

         

         MongoCollection<Document> collection = mongoDatabase.getCollection("test");

         System.out.println("集合 test 选择成功");

         //插入文档  

         /**

         * 1. 创建文档 org.bson.Document 参数为key-value的格式

         * 2. 创建文档集合List<Document>

         * 3. 将文档集合插入数据库集合中 mongoCollection.insertMany(List<Document>) 插入单个文档可以用 mongoCollection.insertOne(Document)

         * */

         Document document = new Document("title", "MongoDB").  

         append("description", "database").  

         append("likes", 100).  

         append("by", "Fly");  

         List<Document> documents = new ArrayList<Document>();  

         documents.add(document);  

         collection.insertMany(documents);  

         System.out.println("文档插入成功");  

      }catch(Exception e){

         System.err.println( e.getClass().getName() + ": " + e.getMessage() );

      }

   }

}

编译运行以上程序,输出结果如下:

Connect to database successfully

集合 test 选择成功

文档插入成功

24.5 检索所有文档

我们可以使用 com.mongodb.client.MongoCollection 类中的 find() 方法来获取集合中的所有文档。

此方法返回一个游标,所以你需要遍历这个游标。

代码片段如下:

import org.bson.Document;

import com.mongodb.MongoClient;

import com.mongodb.client.FindIterable;

import com.mongodb.client.MongoCollection;

import com.mongodb.client.MongoCursor;

import com.mongodb.client.MongoDatabase;

public class MongoDBJDBC{

   public static void main( String args[] ){

      try{   

         // 连接到 mongodb 服务

         MongoClient mongoClient = new MongoClient( "localhost" , 27017 );

         

         // 连接到数据库

         MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");  

         System.out.println("Connect to database successfully");

         

         MongoCollection<Document> collection = mongoDatabase.getCollection("test");

         System.out.println("集合 test 选择成功");

         

         //检索所有文档  

         /**

         * 1. 获取迭代器FindIterable<Document>

         * 2. 获取游标MongoCursor<Document>

         * 3. 通过游标遍历检索出的文档集合

         * */  

         FindIterable<Document> findIterable = collection.find();  

         MongoCursor<Document> mongoCursor = findIterable.iterator();  

         while(mongoCursor.hasNext()){  

            System.out.println(mongoCursor.next());  

         }  

      

      }catch(Exception e){

         System.err.println( e.getClass().getName() + ": " + e.getMessage() );

      }

   }

}

编译运行以上程序,输出结果如下:

Connect to database successfully

集合 test 选择成功

Document{{_id=56e65fb1fd57a86304fe2692, title=MongoDB, description=database, likes=100, by=Fly}}

24.6 更新文档

你可以使用 com.mongodb.client.MongoCollection 类中的 updateMany() 方法来更新集合中的文档。

代码片段如下:

import org.bson.Document;

import com.mongodb.MongoClient;

import com.mongodb.client.FindIterable;

import com.mongodb.client.MongoCollection;

import com.mongodb.client.MongoCursor;

import com.mongodb.client.MongoDatabase;

import com.mongodb.client.model.Filters;

public class MongoDBJDBC{

   public static void main( String args[] ){

      try{   

         // 连接到 mongodb 服务

         MongoClient mongoClient = new MongoClient( "localhost" , 27017 );

         

         // 连接到数据库

         MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");  

         System.out.println("Connect to database successfully");

         

         MongoCollection<Document> collection = mongoDatabase.getCollection("test");

         System.out.println("集合 test 选择成功");

         

         //更新文档   将文档中likes=100的文档修改为likes=200   

         collection.updateMany(Filters.eq("likes", 100), new Document("$set",new Document("likes",200)));  

         //检索查看结果  

         FindIterable<Document> findIterable = collection.find();  

         MongoCursor<Document> mongoCursor = findIterable.iterator();  

         while(mongoCursor.hasNext()){  

            System.out.println(mongoCursor.next());  

         }  

      

      }catch(Exception e){

         System.err.println( e.getClass().getName() + ": " + e.getMessage() );

      }

   }

}

编译运行以上程序,输出结果如下:

Connect to database successfully

集合 test 选择成功

Document{{_id=56e65fb1fd57a86304fe2692, title=MongoDB, description=database, likes=200, by=Fly}}

24.7 删除第一个文档

要删除集合中的第一个文档,首先你需要使用com.mongodb.DBCollection类中的 findOne()方法来获取第一个文档,然后使用remove 方法删除。

代码片段如下:

import org.bson.Document;

import com.mongodb.MongoClient;

import com.mongodb.client.FindIterable;

import com.mongodb.client.MongoCollection;

import com.mongodb.client.MongoCursor;

import com.mongodb.client.MongoDatabase;

import com.mongodb.client.model.Filters;

public class MongoDBJDBC{

   public static void main( String args[] ){

      try{   

         // 连接到 mongodb 服务

         MongoClient mongoClient = new MongoClient( "localhost" , 27017 );

         // 连接到数据库

         MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");  

         System.out.println("Connect to database successfully");

         MongoCollection<Document> collection = mongoDatabase.getCollection("test");

         System.out.println("集合 test 选择成功");

         //删除符合条件的第一个文档  

         collection.deleteOne(Filters.eq("likes", 200));  

         //删除所有符合条件的文档  

         collection.deleteMany (Filters.eq("likes", 200));  

         //检索查看结果  

         FindIterable<Document> findIterable = collection.find();  

         MongoCursor<Document> mongoCursor = findIterable.iterator();  

         while(mongoCursor.hasNext()){  

           System.out.println(mongoCursor.next());  

         }  

           

      }catch(Exception e){

        System.err.println( e.getClass().getName() + ": " + e.getMessage() );

     }

   }

}

编译运行以上程序,输出结果如下:

Connect to database successfully

集合 test 选择成功

25. MongoDB PHP 扩展

本教程将向大家介绍如何在Linux、window、Mac平台上安装MongoDB扩展。

25.1 Linux 上安装 MongoDB PHP 扩展

在终端上安装

你可以在 Linux 中执行以下命令来安装 MongoDB 的 PHP 扩展驱动

$ sudo pecl install mongo

使用php的pecl安装命令必须保证网络连接可用以及root权限。

安装手册

如果你想通过源码来编译扩展驱动。你必须手动编译源码包,这样做的好是最新修正的bug包含在源码包中。

你可以在 PHP 官网上下载 MongoDB PHP 驱动包,下载地址:http://pecl.php.net/package/mongodb

完整安装命令如下:

$ wget http://pecl.php.net/get/mongodb-1.5.2.tgz

$ cd /mongodb-1.5.2

$ phpize

$ ./configure

$ make && make install

如果你的 php 是自己编译的,则安装方法如下(假设是编译在 /usr/local/php目录中):

$ wget http://pecl.php.net/get/mongodb-1.5.2.tgz

$ cd /mongodb-1.5.2

$ /usr/local/php/bin/phpize

$ ./configure --with-php-config=/usr/local/php/bin/php-config

$ make && make install

安装成功后,会有类似以下安装目录信息输出:

...

Installing shared extensions:     /usr/lib/php/extensions/debug-non-zts-20151012/

执行以上命令后,你需要修改php.ini文件,在 php.ini 文件中添加mongo配置,配置如下:

extension_dir=/usr/lib/php/extensions/debug-non-zts-20151012/

extension=mongodb.so

注意:你需要指明 extension_dir 配置项的路径。

可以通过以下命令查看目录地址:

$ php -i | grep extension_dir

  extension_dir => /usr/lib/php/extensions/debug-non-zts-20151012 =>

                   /usr/lib/php/extensions/debug-non-zts-20151012

25.2 Window 上安装 MongoDB PHP扩展

PECL 上已经提供了用于 Window 平台的预编译 php mongodb 驱动二进制包(下载地址: https://pecl.php.net/package/mongodb),你可以下载与你php对应的版本,但是你需要注意以下几点问题:

VC6 是运行于 Apache 服务器

'Thread safe'(线程安全)是运行在Apache上以模块的PHP上,如果你以CGI的模式运行PHP,请选择非线程安全模式(' non-thread safe')。

VC9是运行于 IIS 服务器上。

下载完你需要的二进制包后,解压压缩包,将 php_mongodb.dll文件添加到你的PHP扩展目录中(ext)。ext目录通常在PHP安装目录下的ext目录。

打开php配置文件 php.ini 添加以下配置:

extension=php_mongodb.dll

重启服务器。

通过浏览器访问phpinfo,如果安装成功,就会看到类型以下的信息:

25.3 MAC 中安装 MongoDB PHP扩展驱动

你可以使用 autoconf 安装 MongoDB PHP 扩展驱动。

你可以使用 Xcode 安装 MongoDB PHP 扩展驱动。

如果你使用 XAMPP,你可以使用以下命令安装 MongoDB PHP 扩展驱动:

sudo /Applications/XAMPP/xamppfiles/bin/pecl install mongo

如果以上命令在XMPP或者MAMP中不起作用,你需要在 Github上下载兼容的预编译包。

然后添加 extension=mongodb.so 配置到你的 php.ini 文件中。

26. MongoDB PHP

在php中使用mongodb你必须使用 mongodb 的 php驱动。

MongoDB PHP在各平台上的安装及驱动包下载请查看:PHP安装MongoDB扩展驱动

如果你使用的是 PHP7,请参阅:PHP7 MongoDB 安装与使用

26.1 确保连接及选择一个数据库

为了确保正确连接,你需要指定数据库名,如果数据库在mongoDB中不存在,mongoDB会自动创建

代码片段如下:

<?php

$m = new MongoClient(); // 连接默认主机和端口为:mongodb://localhost:27017

$db = $m->test; // 获取名称为 "test" 的数据库

?>

26.2 创建集合

创建集合的代码片段如下:

<?php

$m = new MongoClient(); // 连接

$db = $m->test; // 获取名称为 "test" 的数据库

$collection = $db->createCollection("runoob");

echo "集合创建成功";

?>

执行以上程序,输出结果如下:

集合创建成功

26.3 插入文档

在mongoDB中使用 insert() 方法插入文档:

插入文档代码片段如下:

<?php

$m = new MongoClient();    // 连接到mongodb

$db = $m->test;            // 选择一个数据库

$collection = $db->runoob; // 选择集合

$document = array(

    "title" => "MongoDB",

    "description" => "database",

    "likes" => 100,

    "url" => "http://www.runoob.com/mongodb/",

    "by", "菜鸟教程"

);

$collection->insert($document);

echo "数据插入成功";

?>

执行以上程序,输出结果如下:

数据插入成功

然后我们在 mongo 客户端使用 db.runoob.find().pretty(); 命令查看数据:

26.4 查找文档

使用find() 方法来读取集合中的文档。

读取使用文档的代码片段如下:

<?php

$m = new MongoClient();    // 连接到mongodb

$db = $m->test;            // 选择一个数据库

$collection = $db->runoob; // 选择集合

$cursor = $collection->find();

// 迭代显示文档标题

foreach ($cursor as $document) {

    echo $document["title"] . "\n";

}

?>

执行以上程序,输出结果如下:

MongoDB

26.5 更新文档

使用 update() 方法来更新文档。

以下实例将更新文档中的标题为' MongoDB 教程', 代码片段如下:

<pre>

<?php

$m = new MongoClient();    // 连接到mongodb

$db = $m->test;            // 选择一个数据库

$collection = $db->runoob; // 选择集合

// 更新文档

$collection->update(array("title"=>"MongoDB"), array('$set'=>array("title"=>"MongoDB 教程")));

// 显示更新后的文档

$cursor = $collection->find();

// 循环显示文档标题

foreach ($cursor as $document) {

    echo $document["title"] . "\n";

}

?>

执行以上程序,输出结果如下:

MongoDB 教程

然后我们在 mongo 客户端使用 db.runoob.find().pretty(); 命令查看数据:

26.6 删除文档

使用 remove() 方法来删除文档。

以下实例中我们将移除 'title' 为 'MongoDB 教程' 的一条数据记录。, 代码片段如下:

<?php

$m = new MongoClient();    // 连接到mongodb

$db = $m->test;            // 选择一个数据库

$collection = $db->runoob; // 选择集合

   

// 移除文档

$collection->remove(array("title"=>"MongoDB 教程"), array("justOne" => true));

// 显示可用文档数据

$cursor = $collection->find();

foreach ($cursor as $document) {

    echo $document["title"] . "\n";

}

?>

除了以上实例外,在php中你还可以使用findOne(), save(), limit(), skip(), sort()等方法来操作Mongodb数据库。

27. PHP7 MongDB 安装与使用

本文教程只适合在 PHP7 的环境,如果你是 PHP5 环境,你可以参阅 PHP MongDB 安装与使用

27.1 PHP7 Mongdb 扩展安装

我们使用 pecl 命令来安装:

$ /usr/local/php7/bin/pecl install mongodb

执行成功后,会输出以下结果:

……

Build process completed successfully

Installing '/usr/local/php7/lib/php/extensions/no-debug-non-zts-20151012/mongodb.so'

install ok: channel://pecl.php.net/mongodb-1.1.7

configuration option "php_ini" is not set to php.ini location

You should add "extension=mongodb.so" to php.ini

接下来我们打开 php.ini 文件,添加 extension=mongodb.so 配置。

可以直接执行以下命令来添加。

$ echo "extension=mongodb.so" >> `/usr/local/php7/bin/php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`

注意:以上执行的命令中 php7 的安装目录为 /usr/local/php7/,如果你安装在其他目录,需要相应修改 pecl 与 php 命令的路径。

27.2 Mongodb 使用

PHP7 连接 MongoDB 语法如下:

$manager = new MongoDB\Driver\Manager("mongodb://localhost:27017");

27.2.1 插入数据

将 name 为"菜鸟教程" 的数据插入到 test 数据库的 runoob 集合中。

<?php

$bulk = new MongoDB\Driver\BulkWrite;

$document = ['_id' => new MongoDB\BSON\ObjectID, 'name' => '菜鸟教程'];

$_id= $bulk->insert($document);

var_dump($_id);

$manager = new MongoDB\Driver\Manager("mongodb://localhost:27017");  

$writeConcern = new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY, 1000);

$result = $manager->executeBulkWrite('test.runoob', $bulk, $writeConcern);

?>

27.2.2 读取数据

这里我们将三个网址数据插入到 test 数据库的 sites 集合,并读取迭代出来:

<?php

$manager = new MongoDB\Driver\Manager("mongodb://localhost:27017");  

// 插入数据

$bulk = new MongoDB\Driver\BulkWrite;

$bulk->insert(['x' => 1, 'name'=>'菜鸟教程', 'url' => 'http://www.runoob.com']);

$bulk->insert(['x' => 2, 'name'=>'Google', 'url' => 'http://www.google.com']);

$bulk->insert(['x' => 3, 'name'=>'taobao', 'url' => 'http://www.taobao.com']);

$manager->executeBulkWrite('test.sites', $bulk);

$filter = ['x' => ['$gt' => 1]];

$options = [

    'projection' => ['_id' => 0],

    'sort' => ['x' => -1],

];

// 查询数据

$query = new MongoDB\Driver\Query($filter, $options);

$cursor = $manager->executeQuery('test.sites', $query);

foreach ($cursor as $document) {

    print_r($document);

}

?>

输出结果为:

stdClass Object

(

    [x] => 3

    [name] => taobao

    [url] => http://www.taobao.com

)

stdClass Object

(

    [x] => 2

    [name] => Google

    [url] => http://www.google.com

)

27.2.3 更新数据

接下来我们将更新 test 数据库 sites 集合中 x 为 2 的数据:

<?php

$bulk = new MongoDB\Driver\BulkWrite;

$bulk->update(

    ['x' => 2],

    ['$set' => ['name' => '菜鸟工具', 'url' => 'tool.runoob.com']],

    ['multi' => false, 'upsert' => false]

);

$manager = new MongoDB\Driver\Manager("mongodb://localhost:27017");  

$writeConcern = new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY, 1000);

$result = $manager->executeBulkWrite('test.sites', $bulk, $writeConcern);

?>

接下来我们使用 "db.sites.find()" 命令查看数据的变化,x 为 2 的数据已经变成了菜鸟工具:

27.2.4 删除数据

以下实例删除了 x 为 1 和 x 为 2的数据,注意 limit 参数的区别:

<?php

$bulk = new MongoDB\Driver\BulkWrite;

$bulk->delete(['x' => 1], ['limit' => 1]);   // limit 为 1 时,删除第一条匹配数据

$bulk->delete(['x' => 2], ['limit' => 0]);   // limit 为 0 时,删除所有匹配数据

$manager = new MongoDB\Driver\Manager("mongodb://localhost:27017");  

$writeConcern = new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY, 1000);

$result = $manager->executeBulkWrite('test.sites', $bulk, $writeConcern);

?>

28. Node.js 连接 MongoDB

MongoDB是一种文档导向数据库管理系统,由C++撰写而成。

本章节我们将为大家介绍如何使用 Node.js 来连接 MongoDB,并对数据库进行操作。

如果你还没有 MongoDB 的基本知识,可以参考我们的教程:MongoDB 教程

28.1 安装驱动

本教程使用了淘宝定制的 cnpm 命令进行安装:

$ cnpm install mongodb

接下来我们来实现增删改查功能。

28.2 创建数据库

要在 MongoDB 中创建一个数据库,首先我们需要创建一个 MongoClient 对象,然后配置好指定的 URL 和 端口号。

如果数据库不存在,MongoDB 将创建数据库并建立连接。

28.3 创建连接

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/runoob"; MongoClient.connect(url, function(err, db) { if (err) throw err; console.log("数据库已创建!"); db.close(); });

28.4 创建集合

我们可以使用 createCollection() 方法来创建集合:

创建集合

var MongoClient = require('mongodb').MongoClient; var url = 'mongodb://localhost:27017/runoob'; MongoClient.connect(url, function (err, db) { if (err) throw err; console.log('数据库已创建'); var dbase = db.db("runoob"); dbase.createCollection('site', function (err, res) { if (err) throw err; console.log("创建集合!"); db.close(); }); });

28.5 数据库操作( CURD )

与 MySQL 不同的是 MongoDB 会自动创建数据库和集合,所以使用前我们不需要手动去创建。

插入数据

以下实例我们连接数据库 runoob 的 site 表,并插入一条数据条数据,使用 insertOne()

插入一条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("runoob"); var myobj = { name: "菜鸟教程", url: "www.runoob" }; dbo.collection("site").insertOne(myobj, function(err, res) { if (err) throw err; console.log("文档插入成功"); db.close(); }); });

执行以下命令输出就结果为:

$ node test.js

文档插入成功

从输出结果来看,数据已插入成功。

我们也可以打开 MongoDB 的客户端查看数据,如:

> show dbs

runoob  0.000GB          # 自动创建了 runoob 数据库

> show tables

site                     # 自动创建了 site 集合(数据表)

> db.site.find()

{ "_id" : ObjectId("5a794e36763eb821b24db854"), "name" : "菜鸟教程", "url" : "www.runoob" }

>

如果要插入多条数据可以使用 insertMany()

插入多条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("runoob"); var myobj = [ { name: '菜鸟工具', url: 'https://c.runoob.com', type: 'cn'}, { name: 'Google', url: 'https://www.google.com', type: 'en'}, { name: 'Facebook', url: 'https://www.google.com', type: 'en'} ]; dbo.collection("site").insertMany(myobj, function(err, res) { if (err) throw err; console.log("插入的文档数量为: " + res.insertedCount); db.close(); }); });

res.insertedCount 为插入的条数。

查询数据

可以使用 find() 来查找数据, find() 可以返回匹配条件的所有数据。 如果未指定条件,find() 返回集合中的所有数据。

find()

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("runoob"); dbo.collection("site"). find({}).toArray(function(err, result) { // 返回集合中所有数据 if (err) throw err; console.log(result); db.close(); }); });

以下实例检索 name 为 "菜鸟教程" 的实例:

查询指定条件的数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("runoob"); var whereStr = {"name":'菜鸟教程'}; // 查询条件 dbo.collection("site").find(whereStr).toArray(function(err, result) { if (err) throw err; console.log(result); db.close(); }); });

执行以下命令输出就结果为:

[ { _id: 5a794e36763eb821b24db854,

    name: '菜鸟教程',

    url: 'www.runoob' } ]

更新数据

我们也可以对数据库的数据进行修改,以下实例将 name 为 "菜鸟教程" 的 url 改为 https://www.runoob.com:

更新一条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("runoob"); var whereStr = {"name":'菜鸟教程'}; // 查询条件 var updateStr = {$set: { "url" : "https://www.runoob.com" }}; dbo.collection("site").updateOne(whereStr, updateStr, function(err, res) { if (err) throw err; console.log("文档更新成功"); db.close(); }); });

执行成功后,进入 mongo 管理工具查看数据已修改:

> db.site.find().pretty()

{

    "_id" : ObjectId("5a794e36763eb821b24db854"),

    "name" : "菜鸟教程",

    "url" : "https://www.runoob.com"     // 已修改为 https

}

如果要更新所有符合条的文档数据可以使用 updateMany()

更新多条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("runoob"); var whereStr = {"type":'en'}; // 查询条件 var updateStr = {$set: { "url" : "https://www.runoob.com" }}; dbo.collection("site").updateMany(whereStr, updateStr, function(err, res) { if (err) throw err; console.log(res.result.nModified + " 条文档被更新"); db.close(); }); });

result.nModified 为更新的条数。

删除数据

以下实例将 name 为 "菜鸟教程" 的数据删除 :

删除一条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("runoob"); var whereStr = {"name":'菜鸟教程'}; // 查询条件 dbo.collection("site").deleteOne(whereStr, function(err, obj) { if (err) throw err; console.log("文档删除成功"); db.close(); }); });

执行成功后,进入 mongo 管理工具查看数据已删除:

> db.site.find()

>

如果要删除多条语句可以使用 deleteMany() 方法

以下实例将 type 为 en 的所有数据删除 :

删除多条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("runoob"); var whereStr = { type: "en" }; // 查询条件 dbo.collection("site").deleteMany(whereStr, function(err, obj) { if (err) throw err; console.log(obj.result.n + " 条文档被删除"); db.close(); }); });

obj.result.n 删除的条数。

排序

排序 使用 sort() 方法,该方法接受一个参数,规定是升序(1)还是降序(-1)。

例如:

{ type: 1 }  // 按 type 字段升序

{ type: -1 } // 按 type 字段降序

按 type 升序排列:

排序

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("runoob"); var mysort = { type: 1 }; dbo.collection("site").find().sort(mysort).toArray(function(err, result) { if (err) throw err; console.log(result); db.close(); }); });

查询分页

如果要设置指定的返回条数可以使用 limit() 方法,该方法只接受一个参数,指定了返回的条数。

limit():读取两条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("runoob"); dbo.collection("site").find().limit(2).toArray(function(err, result) { if (err) throw err; console.log(result); db.close(); }); });

如果要指定跳过的条数,可以使用 skip() 方法。

skip(): 跳过前面两条数据,读取两条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("runoob"); dbo.collection("site").find().skip(2).limit(2).toArray(function(err, result) { if (err) throw err; console.log(result); db.close(); }); });

连接操作

mongoDB 不是一个关系型数据库,但我们可以使用 $lookup 来实现左连接。

例如我们有两个集合数据分别为:

集合1:orders

[

  { _id: 1, product_id: 154, status: 1 }

]

集合2:products

[

  { _id: 154, name: '笔记本电脑' },

  { _id: 155, name: '耳机' },

  { _id: 156, name: '台式电脑' }

]

$lookup 实现左连接

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://127.0.0.1:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("runoob"); dbo.collection('orders').aggregate([ { $lookup: { from: 'products', // 右集合 localField: 'product_id', // 左集合 join 字段 foreignField: '_id', // 右集合 join 字段 as: 'orderdetails' // 新生成字段(类型array) } } ]).toArray(function(err, res) { if (err) throw err; console.log(JSON.stringify(res)); db.close(); }); });

删除集合

我们可以使用 drop() 方法来删除集合:

drop()

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("runoob"); // 删除 test 集合 dbo.collection("test").drop(function(err, delOK) { // 执行成功 delOK 返回 true,否则返回 false if (err) throw err; if (delOK) console.log("集合已删除"); db.close(); }); });

29. MongoDB 关系

MongoDB 的关系表示多个文档之间在逻辑上的相互联系。

文档间可以通过嵌入和引用来建立联系。

MongoDB 中的关系可以是:

1:1 (1对1)

1: N (1对多)

N: 1 (多对1)

N: N (多对多)

接下来我们来考虑下用户与用户地址的关系。

一个用户可以有多个地址,所以是一对多的关系。

以下是 user 文档的简单结构:

{

   "_id":ObjectId("52ffc33cd85242f436000001"),

   "name": "Tom Hanks",

   "contact": "987654321",

   "dob": "01-01-1991"

}

以下是 address 文档的简单结构:

{

   "_id":ObjectId("52ffc4a5d85242602e000000"),

   "building": "22 A, Indiana Apt",

   "pincode": 123456,

   "city": "Los Angeles",

   "state": "California"

}

29.1 嵌入式关系

使用嵌入式方法,我们可以把用户地址嵌入到用户的文档中:

   "_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"

      }]

}

以上数据保存在单一的文档中,可以比较容易的获取和维护数据。 你可以这样查询用户的地址:

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

注意:以上查询中 db 和 users 表示数据库和集合。

这种数据结构的缺点是,如果用户和用户地址在不断增加,数据量不断变大,会影响读写性能。

29.2 引用式关系

引用式关系是设计数据库时经常用到的方法,这种方法把用户数据文档和用户地址数据文档分开,通过引用文档的 id 字段来建立关系。

{

   "_id":ObjectId("52ffc33cd85242f436000001"),

   "contact": "987654321",

   "dob": "01-01-1991",

   "name": "Tom Benzamin",

   "address_ids": [

      ObjectId("52ffc4a5d85242602e000000"),

      ObjectId("52ffc4a5d85242602e000001")

   ]

}

以上实例中,用户文档的 address_ids 字段包含用户地址的对象id(ObjectId)数组。

我们可以读取这些用户地址的对象id(ObjectId)来获取用户的详细地址信息。

这种方法需要两次查询,第一次查询用户地址的对象id(ObjectId),第二次通过查询的id获取用户的详细地址信息。

>var result = db.users.findOne({"name":"Tom Benzamin"},{"address_ids":1})

>var addresses = db.address.find({"_id":{"$in":result["address_ids"]}})

30. MongoDB 数据库引用

在上一章节MongoDB关系中我们提到了MongoDB的引用来规范数据结构文档。

MongoDB 引用有两种:

手动引用(Manual References)

DBRefs

30.1 DBRefs vs 手动引用

考虑这样的一个场景,我们在不同的集合中 (address_home, address_office, address_mailing, 等)存储不同的地址(住址,办公室地址,邮件地址等)。

这样,我们在调用不同地址时,也需要指定集合,一个文档从多个集合引用文档,我们应该使用 DBRefs。

30.2 使用 DBRefs

DBRef的形式:

{ $ref : , $id : , $db :  }

三个字段表示的意义为:

$ref:集合名称

$id:引用的id

$db:数据库名称,可选参数

以下实例中用户数据文档使用了 DBRef, 字段 address:

{

   "_id":ObjectId("53402597d852426020000002"),

   "address": {

   "$ref": "address_home",

   "$id": ObjectId("534009e4d852427820000002"),

   "$db": "runoob"},

   "contact": "987654321",

   "dob": "01-01-1991",

   "name": "Tom Benzamin"

}

address DBRef 字段指定了引用的地址文档是在 runoob 数据库下的 address_home 集合,id 为 534009e4d852427820000002。

以下代码中,我们通过指定 $ref 参数(address_home 集合)来查找集合中指定id的用户地址信息:

>var user = db.users.findOne({"name":"Tom Benzamin"})

>var dbRef = user.address

>db[dbRef.$ref].findOne({"_id":(dbRef.$id)})

以上实例返回了 address_home 集合中的地址数据:

{

   "_id" : ObjectId("534009e4d852427820000002"),

   "building" : "22 A, Indiana Apt",

   "pincode" : 123456,

   "city" : "Los Angeles",

   "state" : "California"

}

31. MongoDB 覆盖索引查询

官方的MongoDB的文档中说明,覆盖查询是以下的查询:

所有的查询字段是索引的一部分

所有的查询返回字段在同一个索引中

由于所有出现在查询中的字段是索引的一部分, MongoDB 无需在整个数据文档中检索匹配查询条件和返回使用相同索引的查询结果。

因为索引存在于RAM中,从索引中获取数据比通过扫描文档读取数据要快得多。

使用覆盖索引查询

为了测试覆盖索引查询,使用以下 users 集合:

{

   "_id": ObjectId("53402597d852426020000002"),

   "contact": "987654321",

   "dob": "01-01-1991",

   "gender": "M",

   "name": "Tom Benzamin",

   "user_name": "tombenzamin"

}

我们在 users 集合中创建联合索引,字段为 gender 和 user_name :

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

现在,该索引会覆盖以下查询:

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

也就是说,对于上述查询,MongoDB的不会去数据库文件中查找。相反,它会从索引中提取数据,这是非常快速的数据查询。

由于我们的索引中不包括 _id 字段,_id在查询中会默认返回,我们可以在MongoDB的查询结果集中排除它。

下面的实例没有排除_id,查询就不会被覆盖:

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

最后,如果是以下的查询,不能使用覆盖索引查询:

所有索引字段是一个数组

所有索引字段是一个子文档

32. MongoDB 查询分析

MongoDB 查询分析可以确保我们建议的索引是否有效,是查询语句性能分析的重要工具。

MongoDB 查询分析常用函数有:explain() 和 hint()。

32.1 使用 explain()

explain 操作提供了查询信息,使用索引及查询统计等。有利于我们对索引的优化。

接下来我们在 users 集合中创建 gender 和 user_name 的索引:

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

现在在查询语句中使用 explain :

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

以上的 explain() 查询返回如下结果:

{

   "cursor" : "BtreeCursor gender_1_user_name_1",

   "isMultiKey" : false,

   "n" : 1,

   "nscannedObjects" : 0,

   "nscanned" : 1,

   "nscannedObjectsAllPlans" : 0,

   "nscannedAllPlans" : 1,

   "scanAndOrder" : false,

   "indexOnly" : true,

   "nYields" : 0,

   "nChunkSkips" : 0,

   "millis" : 0,

   "indexBounds" : {

      "gender" : [

         [

            "M",

            "M"

         ]

      ],

      "user_name" : [

         [

            {

               "$minElement" : 1

            },

            {

               "$maxElement" : 1

            }

         ]

      ]

   }

}

现在,我们看看这个结果集的字段:

indexOnly: 字段为 true ,表示我们使用了索引。

cursor:因为这个查询使用了索引,MongoDB 中索引存储在B树结构中,所以这是也使用了 BtreeCursor 类型的游标。如果没有使用索引,游标的类型是 BasicCursor。这个键还会给出你所使用的索引的名称,你通过这个名称可以查看当前数据库下的system.indexes集合(系统自动创建,由于存储索引信息,这个稍微会提到)来得到索引的详细信息。

n:当前查询返回的文档数量。

nscanned/nscannedObjects:表明当前这次查询一共扫描了集合中多少个文档,我们的目的是,让这个数值和返回文档的数量越接近越好。

millis:当前查询所需时间,毫秒数。

indexBounds:当前查询具体使用的索引。

1、queryPlanner
queryPlanner是现版本explain的默认模式。这种模式并不会真正进行query语句查询,而是针对query语句进行执行计划分析并选出winning plan。

"queryPlanner" : {
   "mongosPlannerVersion" : <int>,
   "winningPlan" : {
      "stage" : <STAGE1>,
      "shards" : [
         {
            "shardName" : <string>,
            "connectionString" : <string>,
            "serverInfo" : {
               "host" : <string>,
               "port" : <int>,
               "version" : <string>,
               "gitVersion" : <string>
            },
            "plannerVersion" : <int>,
            "namespace" : <string>,
            "parsedQuery" : <document>,
            "queryHash" : <hexadecimal string>,
            "planCacheKey" : <hexadecimal string>,
            "optimizedPipeline" : <boolean>, // Starting in MongoDB 4.2, only appears if true
            "winningPlan" : {
               "stage" : <STAGE2>,
               "inputStage" : {
                  "stage" : <STAGE3>
                  ...,
               }
            },
            "rejectedPlans" : [
               <candidate plan 1>,
               ...
            ]
         },
         ...
      ]
   }
}

explain.queryPlanner

    包含由查询优化器选择的查询计划信息。

    explain.queryPlanner.namespace

        指明当前查询语句操作的命令空间(i.e.,<database>.<colleciton>)

    explain.queryPlanner.indexFilterSet

        一个布尔值表名mongodb是否使用 index filter 

注:Index filter决定了优化器为query shape所评估的索引(们)。

    explain.queryPlanner.queryHash

        一个十六进制字符串用于表名query shape的hash,它仅仅取决于query shapes。

    explain.queryPlanner.winningPlan

        查询优化器针对该query所返回的最优执行计划的详细内容。mongodb将计划呈现为一个阶段树;即,一个阶段可以有一个inputStage,如果一个阶段有多个子阶段就是inputStages。

        explain.queryPlanner.winningPlan.stage

            阶段名。每个阶段都包含特定于该阶段的信息。例如IXSCAN阶段将包括索引边界以及特定于索引扫描的其他数据。如果一个阶段有一个子阶段或者多个子阶段,则该阶段将有一个或多个inputStage。

        explain.queryPlanner.winningPlan.inputStage

            描述子阶段的文档,它向其父阶段提供文档或索引键(???)。如果父阶段只有一个子阶段则此字段存在。

        explain.queryPlanner.winningPlan.inputStages

            描述诸多子阶段的文档数组。子阶段们为父阶段提供文档或索引键。如果父阶段有多个子阶段,则该字段存在。例如, $or expressions 或 index intersection 阶段就会消费来自多个来源的输入。

注:关于索引键这个东西最好还是深入的了解下mongodb的索引实现这块东西。

MongoDB索引原理 | MongoDB中文社区

Indexes — MongoDB Manual

2、executionStats
executionStats返回的是获胜计划的执行细节。必须在executionStats或allPlansExcution详细模式下运行的explain才显示相关信息。

"executionStats" : {
   "nReturned" : <int>,
   "executionTimeMillis" : <int>,
   "totalKeysExamined" : <int>,
   "totalDocsExamined" : <int>,
   "executionStages" : {
      "stage" : <STAGE1>
      "nReturned" : <int>,
      "executionTimeMillis" : <int>,
      "totalKeysExamined" : <int>,
      "totalDocsExamined" : <int>,
      "totalChildMillis" : <NumberLong>,
      "shards" : [
         {
            "shardName" : <string>,
            "executionSuccess" : <boolean>,
            "executionStages" : {
               "stage" : <STAGE2>,
               "nReturned" : <int>,
               "executionTimeMillisEstimate" : <int>,
               ...
               "chunkSkips" : <int>,
               "inputStage" : {
                  "stage" : <STAGE3>,
                  ...
                  "inputStage" : {
                     ...
                  }
               }
            }
         },
         ...
      ]
   }
   "allPlansExecution" : [
      {
         "shardName" : <string>,
         "allPlans" : [
            {
               "nReturned" : <int>,
               "executionTimeMillisEstimate" : <int>,
               "totalKeysExamined" : <int>,
               "totalDocsExamined" :<int>,
               "executionStages" : {
                  "stage" : <STAGEA>,
                  "nReturned" : <int>,
                  "executionTimeMillisEstimate" : <int>,
                  ...
                  "inputStage" : {
                     "stage" : <STAGEB>,
                     ...
                     "inputStage" : {
                       ...
                     }
                  }
               }
            },
            ...
         ]
      },
      {
         "shardName" : <string>,
         "allPlans" : [
          ...
         ]
      },
      ...
   ]
}


explain.executionStats

    描述获胜计划(winning plan)完整的查询执行数据。对于写操作,是指讲些执行修改的信息,但是并不会真的修改数据库。

    explain.executionStats.nReturned

        查询条件匹配到的文档数量。

    explain.executionStats.executionTimeMillis

        被选择的查询计划所需的总时间(毫秒为单位)。对应于早期版本mongodb中cursor.explain()返回的millis字段。

    explain.executionStats.totalKeysExamined

        扫描的索引项数(索引扫描条目)。totalKeysExamined 对应于早期版本的cursor.explaom()返回的nscanned字段。

    explain.executionStats.totalDocsExamined

        扫描的文档数。检查文档常用的查询执行阶段是COLLSCAN和FETCH。 

        注意:①totalDocsExamined是指检查的文档总数,而不是返回的文档数。②如果在查询执行期间多次检查文档,则totalDocsExamined会对每次检查进行计数。也就是说,totalDocsExamined不是检查的唯一文档总数的计数。

    explain.executionStats.executionStages

        以阶段树的形式详细说明已完成的获胜计划(winning plan);即一个阶段可以有一个inputStage或者多个inputStages。每个stage都包含特定于该stage的执行信息。

    explain.executionStats.executionStages.executionTimeMillisEstimate

        查询执行的预估时间(单位毫秒)

    explain.executionStats.executionStages.works

        表明查询执行stage执行的“work units”数。查询执行将其工作划分为几个小单元。“work units”可能包括检查单个索引键、从集合中获取单个文档、对单个文档应用projection或执行一段内部簿记等。

    explain.executionStats.executionStages.advanced

        中间结果返回的次数。或者这个stage对其父stage的“advanced”。

    explain.executionStats.executionStages.needTime

        未将中间结果“advance”到期父stage的工作循环数。例如,索引扫描阶段可能花费一个工作周期来寻找索引中的一个新位置,而不是返回索引键;这个工作周期将会计入needTime而不是advanced。

    explain.executionStats.executionStages.needYield

        存储层请求查询阶段暂停处理并产生锁的次数。

    explain.executionStats.executionStages.saveState

        查询阶段挂起处理并保存其当前执行状态的次数,例如为产生锁做准备的次数。

    explain.executionStats.executionStages.isEOF

        用于表名执行阶段是否已达到流的结尾。true或者1,则执行阶段已达到流的结尾。false或者0,则阶段可能仍有结果要返回。举个例子。考虑一个具有limit的查询,其执行阶段由limit阶段组成,该查询的输入阶段为IXSCAN。如果查询返回的值超过指定的限制,则limit阶段将报告isEOF:1,但其基础IXSCAN阶段将报告isEOF:0。

    explain.executionStats.executionStages.inputStage.keysExamined

        对于扫描索引的查询执行阶段(例如IXSCAN),keysExamined是索引扫描过程中检查的in-bounds键和out-of-bounds键的总数。如果索引扫描包含单个连续的键范围,则只需要检查边界内的键。如果索引边界由多个键范围组成,则索引扫描执行过程可以检查超出边界的键,以便从一个范围的末尾跳到下一个范围的开头。

       考虑以下示例,其中有一个字段x的索引,集合包含100个x值为1到100的文档:

db.keys.find( { x : { $in : [ 3, 4, 50, 74, 75, 90 ] } } ).explain( "executionStats" )
       查询将扫描键3和4。随后它将扫描键5,检测到它out-of-bounds,然后跳到下一个键50。

        继续此过程,查询将扫描键3、4、5、50、51、74、75、76、90和91。键5、51、76和91是仍在检查的out-of-bounds。keysExamined值为10。

    explain.executionStats.executionStages.inputStage.docsExamined

        指定在查询执行阶段扫描的文档数。用于COLLSCAN阶段,以及从集合中retrieve文档的阶段(例如FETCH)。

    explain.executionStats.executionStages.inputStage.seeks

        为了完成索引扫描我们不得不为了新位置去寻找索引光标的次数。 版本3.4中的新功能:仅用于索引扫描(IXSCAN)阶段。

    explain.executionStats.executionStages.inputStage.allPlansExecution

        包含在计划选择阶段为成功计划和拒绝计划捕获的部分执行信息。

          

3、allPlansExecution
4、stage状态分析
    那么又是什么影响到了totalKeysExamined和totalDocsExamined?是stage的类型。类型列举如下:

    COLLSCAN:全表扫描

    IXSCAN:索引扫描

    FETCH:根据索引去检索指定document

    SHARD_MERGE:将各个分片返回数据进行merge

    SORT:表明在内存中进行了排序

    LIMIT:使用limit限制返回数

    SKIP:使用skip进行跳过

    IDHACK:针对_id进行查询

    SHARDING_FILTER:通过mongos对分片数据进行查询

    COUNT:利用db.coll.explain().count()之类进行count运算

    COUNTSCAN:count不使用Index进行count时的stage返回

    COUNT_SCAN:count使用了Index进行count时的stage返回

    SUBPLA:未使用到索引的$or查询的stage返回

    TEXT:使用全文索引进行查询时候的stage返回

    PROJECTION:限定返回字段时候stage的返回

    对于普通查询,我希望看到stage的组合(查询的时候尽可能用上索引):

    Fetch+IDHACK

    Fetch+ixscan

    Limit+(Fetch+ixscan)

    PROJECTION+ixscan

    SHARDING_FITER+ixscan

    COUNT_SCAN

    不希望看到包含如下的stage:

    COLLSCAN(全表扫描),SORT(使用sort但是无index),不合理的SKIP,SUBPLA(未用到index的$or),COUNTSCAN(不使用index进行count)
 

32.2 使用 hint()

虽然MongoDB查询优化器一般工作的很不错,但是也可以使用 hint 来强制 MongoDB 使用一个指定的索引。

这种方法某些情形下会提升性能。 一个有索引的 collection 并且执行一个多字段的查询(一些字段已经索引了)。

如下查询实例指定了使用 gender 和 user_name 索引字段来查询:

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

可以使用 explain() 函数来分析以上查询:

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

33. MongoDB 原子操作

mongodb不支持事务,所以,在你的项目中应用时,要注意这点。无论什么设计,都不要要求mongodb保证数据的完整性。

但是mongodb提供了许多原子操作,比如文档的保存,修改,删除等,都是原子操作。

所谓原子操作就是要么这个文档保存到Mongodb,要么没有保存到Mongodb,不会出现查询到的文档没有保存完整的情况。

33.1 原子操作数据模型

考虑下面的例子,图书馆的书籍及结账信息。

实例说明了在一个相同的文档中如何确保嵌入字段关联原子操作(update:更新)的字段是同步的。

book = {

          _id: 123456789,

          title: "MongoDB: The Definitive Guide",

          author: [ "Kristina Chodorow", "Mike Dirolf" ],

          published_date: ISODate("2010-09-24"),

          pages: 216,

          language: "English",

          publisher_id: "oreilly",

          available: 3,

          checkout: [ { by: "joe", date: ISODate("2012-10-15") } ]

        }

你可以使用 db.collection.findAndModify() 方法来判断书籍是否可结算并更新新的结算信息。

在同一个文档中嵌入的 available 和 checkout 字段来确保这些字段是同步更新的:

db.books.findAndModify ( {

   query: {

            _id: 123456789,

            available: { $gt: 0 }

          },

   update: {

             $inc: { available: -1 },

             $push: { checkout: { by: "abc", date: new Date() } }

           }

} )

33.2 原子操作常用命令

$set

用来指定一个键并更新键值,若键不存在并创建。

{ $set : { field : value } }

$unset

用来删除一个键。

{ $unset : { field : 1} }

$inc

$inc可以对文档的某个值为数字型(只能为满足要求的数字)的键进行增减的操作。

{ $inc : { field : value } }

$push

用法:

{ $push : { field : value } }

把value追加到field里面去,field一定要是数组类型才行,如果field不存在,会新增一个数组类型加进去。

$pushAll

同$push,只是一次可以追加多个值到一个数组字段内。

{ $pushAll : { field : value_array } }

$pull

从数组field内删除一个等于value值。

{ $pull : { field : _value } }

$addToSet

增加一个值到数组内,而且只有当这个值不在数组内才增加。

$pop

删除数组的第一个或最后一个元素

{ $pop : { field : 1 } }

$rename

修改字段名称

{ $rename : { old_field_name : new_field_name } }

$bit

位操作,integer类型

{$bit : { field : {and : 5}}}

偏移操作符

> t.find() { "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 3 }, { "by" : "jane", "votes" : 7 } ] }

 

> t.update( {'comments.by':'joe'}, {$inc:{'comments.$.votes':1}}, false, true )

 

> t.find() { "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 7 } ] }

34. MongoDB 高级索引

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

{

   "address": {

      "city": "Los Angeles",

      "state": "California",

      "pincode": "123"

   },

   "tags": [

      "music",

      "cricket",

      "blogs"

   ],

   "name": "Tom Benzamin"

}

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

34.1 索引数组字段

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

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

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

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

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

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

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

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

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

34.2 索引子文档字段

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

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

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

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

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

查询表达不一定遵循指定的索引的顺序,mongodb 会自动优化。所以上面创建的索引将支持以下查询:

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

同样支持以下查询:

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

35. MongoDB 索引限制

35.1 额外开销

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

35.2 内存(RAM)使用

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

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

35.3 查询限制

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

正则表达式及非操作符,如 $nin, $not, 等。

算术运算符,如 $mod, 等。

$where 子句

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

35.4 索引键限制

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

35.5 插入文档超过索引键限制

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

35.6 最大范围

集合中索引不能超过64个

索引名的长度不能超过128个字符

一个复合索引最多可以有31个字段

36. MongoDB ObjectId

在前面几个章节中我们已经使用了MongoDB 的对象 Id(ObjectId)。

在本章节中,我们将了解的ObjectId的结构。

ObjectId 是一个12字节 BSON 类型数据,有以下格式:

前4个字节表示时间戳

接下来的3个字节是机器标识码

紧接的两个字节由进程id组成(PID)

最后三个字节是随机数。

MongoDB中存储的文档必须有一个"_id"键。这个键的值可以是任何类型的,默认是个ObjectId对象。

在一个集合里面,每个文档都有唯一的"_id"值,来确保集合里面每个文档都能被唯一标识。

MongoDB采用ObjectId,而不是其他比较常规的做法(比如自动增加的主键)的主要原因,因为在多个 服务器上同步自动增加主键值既费力还费时。

36.1 创建新的ObjectId

使用以下代码生成新的ObjectId:

>newObjectId = ObjectId()

上面的语句返回以下唯一生成的id:

ObjectId("5349b4ddd2781d08c09890f3")

你也可以使用生成的id来取代MongoDB自动生成的ObjectId:

>myObjectId = ObjectId("5349b4ddd2781d08c09890f4")

36.2 创建文档的时间戳

由于 ObjectId 中存储了 4 个字节的时间戳,所以你不需要为你的文档保存时间戳字段,你可以通过 getTimestamp 函数来获取文档的创建时间:

>ObjectId("5349b4ddd2781d08c09890f4").getTimestamp()

以上代码将返回 ISO 格式的文档创建时间:

ISODate("2014-04-12T21:49:17Z")

36.3 ObjectId 转换为字符串

在某些情况下,您可能需要将ObjectId转换为字符串格式。你可以使用下面的代码:

>new ObjectId().str

以上代码将返回Guid格式的字符串::

5349b4ddd2781d08c09890f3

37. MongoDB Map Reduce

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

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

37.1 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的用处不大)

以下实例在集合 orders 中查找 status:"A" 的数据,并根据 cust_id 来分组,并计算 amount 的总和。

37.2 使用 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

}

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

具体参数说明:

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的使用非常灵活和强大。

38. MongoDB 正则表达式

正则表达式是使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。

许多程序设计语言都支持利用正则表达式进行字符串操作。

MongoDB 使用 $regex 操作符来设置匹配字符串的正则表达式。

MongoDB使用PCRE (Perl Compatible Regular Expression) 作为正则表达式语言。

不同于全文检索,我们使用正则表达式不需要做任何配置。

考虑以下 posts 集合的文档结构,该文档包含了文章内容和标签:

{

   "post_text": "enjoy the mongodb articles on runoob",

   "tags": [

      "mongodb",

      "runoob"

   ]

}

38.1 使用正则表达式

以下命令使用正则表达式查找包含 runoob 字符串的文章:

>db.posts.find({post_text:{$regex:"runoob"}})

以上查询也可以写为:

>db.posts.find({post_text:/runoob/})

38.2 不区分大小写的正则表达式

如果检索需要不区分大小写,我们可以设置 $options 为 $i。

以下命令将查找不区分大小写的字符串 runoob:

>db.posts.find({post_text:{$regex:"runoob",$options:"$i"}})

集合中会返回所有包含字符串 runoob 的数据,且不区分大小写:

{

   "_id" : ObjectId("53493d37d852429c10000004"),

   "post_text" : "hey! this is my post on  runoob",

   "tags" : [ "runoob" ]

}

38.3 数组元素使用正则表达式

我们还可以在数组字段中使用正则表达式来查找内容。 这在标签的实现上非常有用,如果你需要查找包含以 run 开头的标签数据(ru 或 run 或 runoob), 你可以使用以下代码:

>db.posts.find({tags:{$regex:"run"}})

38.4 优化正则表达式查询

如果你的文档中字段设置了索引,那么使用索引相比于正则表达式匹配查找所有的数据查询速度更快。

如果正则表达式是前缀表达式,所有匹配的数据将以指定的前缀字符串为开始。例如: 如果正则表达式为 ^tut ,查询语句将查找以 tut 为开头的字符串。

这里面使用正则表达式有两点需要注意:

正则表达式中使用变量。一定要使用eval将组合的字符串进行转换,不能直接将字符串拼接后传入给表达式。否则没有报错信息,只是结果为空!实例如下:

var name=eval("/" + 变量值key +"/i");

以下是模糊查询包含title关键词, 且不区分大小写:

title:eval("/"+title+"/i")    // 等同于 title:{$regex:title,$Option:"$i"}   

39. MongoDB 管理工具: Rockmongo

RockMongo是PHP5写的一个MongoDB管理工具。

通过 Rockmongo 你可以管理 MongoDB服务,数据库,集合,文档,索引等等。

它提供了非常人性化的操作。类似 phpMyAdmin(PHP开发的MySql管理工具)。

Rockmongo 下载地址:下载 - GoEdge 

39.1 简介

主要特征:

使用宽松的New BSD License协议

速度快,安装简单

支持多语言(目前提供中文、英文、日文、巴西葡萄牙语、法语、德语、俄语、意大利语)

系统

可以配置多个主机,每个主机可以有多个管理员

需要管理员密码才能登入操作,确保数据库的安全性

服务器

服务器信息 (WEB服务器, PHP, PHP.ini相关指令 ...)

状态

数据库信息

数据库

查询,创建和删除

执行命令和Javascript代码

统计信息

集合(相当于表)

强大的查询工具

读数据,写数据,更改数据,复制数据,删除数据

查询、创建和删除索引

清空数据

批量删除和更改数据

统计信息

GridFS

查看分块

下载文件

39.2 安装

需求

一个能运行PHP的Web服务器,比如Apache Httpd, Nginx ...

PHP - 需要PHP v5.1.6或更高版本,需要支持SESSION

为了能连接MongoDB,你需要安装php_mongo扩展

快速安装

下载安装包

解压到你的网站目录下

用编辑器打开config.php,修改host, port, admins等参数

在浏览器中访问index.php,比如说:http://localhost/rockmongo/index.php

使用用户名和密码登录,默认为"admin"和"admin"

开始玩转MongoDB!

40. MongoDB GridFS

GridFS 用于存储和恢复那些超过16M(BSON文件限制)的文件(如:图片、音频、视频等)。

GridFS 也是文件存储的一种方式,但是它是存储在MonoDB的集合中。

GridFS 可以更好的存储大于16M的文件。

GridFS 会将大文件对象分割成多个小的chunk(文件片段),一般为256k/个,每个chunk将作为MongoDB的一个文档(document)被存储在chunks集合中。

GridFS 用两个集合来存储一个文件:fs.files与fs.chunks。

每个文件的实际内容被存在chunks(二进制数据)中,和文件有关的meta数据(filename,content_type,还有用户自定义的属性)将会被存在files集合中。

以下是简单的 fs.files 集合文档:

{

   "filename": "test.txt",

   "chunkSize": NumberInt(261120),

   "uploadDate": ISODate("2014-04-13T11:32:33.557Z"),

   "md5": "7b762939321e146569b07f72c62cca4f",

   "length": NumberInt(646)

}

以下是简单的 fs.chunks 集合文档:

{

   "files_id": ObjectId("534a75d19f54bfec8a2fe44b"),

   "n": NumberInt(0),

   "data": "Mongo Binary Data"

}

GridFS 添加文件

现在我们使用 GridFS 的 put 命令来存储 mp3 文件。 调用 MongoDB 安装目录下bin的 mongofiles.exe工具。

打开命令提示符,进入到MongoDB的安装目录的bin目录中,找到mongofiles.exe,并输入下面的代码:

>mongofiles.exe -d gridfs put song.mp3

GridFS 是存储文件的数据名称。如果不存在该数据库,MongoDB会自动创建。Song.mp3 是音频文件名。

使用以下命令来查看数据库中文件的文档:

>db.fs.files.find()

以上命令执行后返回以下文档数据:

{

   _id: ObjectId('534a811bf8b4aa4d33fdf94d'),

   filename: "song.mp3",

   chunkSize: 261120,

   uploadDate: new Date(1397391643474), md5: "e4f53379c909f7bed2e9d631e15c1c41",

   length: 10401959

}

我们可以看到 fs.chunks 集合中所有的区块,以下我们得到了文件的 _id 值,我们可以根据这个 _id 获取区块(chunk)的数据:

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

以上实例中,查询返回了 40 个文档的数据,意味着mp3文件被存储在40个区块中。

41. MongoDB 固定集合(Capped Collections)

MongoDB 固定集合(Capped Collections)是性能出色且有着固定大小的集合,对于大小固定,我们可以想象其就像一个环形队列,当集合空间用完后,再插入的元素就会覆盖最初始的头部的元素!

41.1 创建固定集合

我们通过createCollection来创建一个固定集合,且capped选项设置为true:

>db.createCollection("cappedLogCollection",{capped:true,size:10000})

还可以指定文档个数,加上max:1000属性:

>db.createCollection("cappedLogCollection",{capped:true,size:10000,max:1000})

判断集合是否为固定集合:

>db.cappedLogCollection.isCapped()

如果需要将已存在的集合转换为固定集合可以使用以下命令:

>db.runCommand({"convertToCapped":"posts",size:10000})

以上代码将我们已存在的 posts 集合转换为固定集合。

41.2 固定集合查询

固定集合文档按照插入顺序储存的,默认情况下查询就是按照插入顺序返回的,也可以使用$natural调整返回顺序。

>db.cappedLogCollection.find().sort({$natural:-1})

41.3 固定集合的功能特点

可以插入及更新,但更新不能超出collection的大小,否则更新失败,不允许删除,但是可以调用drop()删除集合中的所有行,但是drop后需要显式地重建集合。

在32位机子上一个cappped collection的最大值约为482.5M,64位上只受系统文件大小的限制。

41.4 固定集合属性及用法

属性

属性1:对固定集合进行插入速度极快

属性2:按照插入顺序的查询输出速度极快

属性3:能够在插入最新数据时,淘汰最早的数据

用法

用法1:储存日志信息

用法2:缓存一些少量的文档

42. MongoDB 自动增长

MongoDB 没有像 SQL 一样有自动增长的功能, MongoDB 的 _id 是系统自动生成的12字节唯一标识。

但在某些情况下,我们可能需要实现 ObjectId 自动增长功能。

由于 MongoDB 没有实现这个功能,我们可以通过编程的方式来实现,以下我们将在 counters 集合中实现_id字段自动增长。

42.1 使用 counters 集合

考虑以下 products 文档。我们希望 _id 字段实现 从 1,2,3,4 到 n 的自动增长功能。

{

  "_id":1,

  "product_name": "Apple iPhone",

  "category": "mobiles"

}

为此,创建 counters 集合,序列字段值可以实现自动长:

>db.createCollection("counters")

现在我们向 counters 集合中插入以下文档,使用 productid 作为 key:

{

  "_id":"productid",

  "sequence_value": 0

}

sequence_value 字段是序列通过自动增长后的一个值。

使用以下命令插入 counters 集合的序列文档中:

>db.counters.insert({_id:"productid",sequence_value:0})

42.2 创建 Javascript 函数

现在,我们创建函数 getNextSequenceValue 来作为序列名的输入, 指定的序列会自动增长 1 并返回最新序列值。在本文的实例中序列名为 productid 。

>function getNextSequenceValue(sequenceName){

   var sequenceDocument = db.counters.findAndModify(

      {

         query:{_id: sequenceName },

         update: {$inc:{sequence_value:1}},

         "new":true

      });

   return sequenceDocument.sequence_value;

}

42.3 使用 Javascript 函数

接下来我们将使用 getNextSequenceValue 函数创建一个新的文档, 并设置文档 _id 自动为返回的序列值:

>db.products.insert({

   "_id":getNextSequenceValue("productid"),

   "product_name":"Apple iPhone",

   "category":"mobiles"})

>db.products.insert({

   "_id":getNextSequenceValue("productid"),

   "product_name":"Samsung S3",

   "category":"mobiles"})

就如你所看到的,我们使用 getNextSequenceValue 函数来设置 _id 字段。

为了验证函数是否有效,我们可以使用以下命令读取文档:

>db.products.find()

以上命令将返回以下结果,我们发现 _id 字段是自增长的:

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

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

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值