2021-11-30 10:04:11

mongodb手册

[TOC]

mongodb, 文档数据库,超高性能。无事务。

安装

osx

# 进入 /usr/local cd /usr/local # 下载 sudo curl -O https://fastdl.mongodb.org/osx/mongodb-osx-ssl-x86_64-4.0.9.tgz # 解压 sudo tar -zxvf mongodb-osx-ssl-x86_64-4.0.9.tgz # 重命名为 mongodb 目录 sudo mv mongodb-osx-x86_64-4.0.9/ mongodb

安装完成后,我们可以把 MongoDB 的二进制命令文件目录(安装目录/bin)添加到 PATH 路径中:

export PATH=/usr/local/mongodb/bin:$PATH

创建日志及数据存放的目录:

  • 数据存放路径:

    sudo mkdir -p /usr/local/var/mongodb
    
  • 日志文件路径:

    sudo mkdir -p /usr/local/var/log/mongodb
    

接下来要确保当前用户对以上两个目录有读写的权限:

sudo chown runoob /usr/local/var/mongodb sudo chown runoob /usr/local/var/log/mongodb

以上 runoob 是我电脑上对用户,你这边需要根据你当前对用户名来修改。

接下来我们使用以下命令在后台启动 mongodb:

mongod --dbpath /usr/local/var/mongodb --logpath /usr/local/var/log/mongodb/mongo.log --fork
  • –dbpath 设置数据存放目录
  • –logpath 设置日志存放目录
  • –fork 在后台运行

如果不想在后端运行,而是在控制台上查看运行过程可以直接设置配置文件启动:

mongod --config /usr/local/etc/mongod.conf

查看 mongod 服务是否启动:

ps aux | grep -v grep | grep mongod

使用以上命令如果看到有 mongod 的记录表示运行成功。

启动后我们可以使用 mongo 命令打开一个终端:

$ cd /usr/local/mongodb/bin $ ./mongo MongoDB shell version v4.0.9 connecting to: mongodb://127.0.0.1:27017/?gssapiServiceName=mongodb Implicit session: session { "id" : UUID("3c12bf4f-695c-48b2-b160-8420110ccdcf") } MongoDB server version: 4.0.9 …… > 1 + 1 2 >

使用 brew 安装

此外你还可以使用 OSX 的 brew 来安装 mongodb:

brew tap mongodb/brew brew install mongodb-community@4.4

@ 符号后面的 4.4 是最新版本号。

安装信息:

  • 配置文件:/usr/local/etc/mongod.conf
  • 日志文件路径:/usr/local/var/log/mongodb
  • 数据存放路径:/usr/local/var/mongodb

运行 MongoDB

我们可以使用 brew 命令或 mongod 命令来启动服务。

brew 启动:

brew services start mongodb-community@4.4

brew 停止:

brew services stop mongodb-community@4.4

mongod 命令后台进程方式:

mongod --config /usr/local/etc/mongod.conf --fork

这种方式启动要关闭可以进入 mongo shell 控制台来实现:

> db.adminCommand({ "shutdown" : 1 })

mongod 启动

默认存到 /data/db

默认端口 27017

同时启动http服务: http://localhost:28017

概念

RDBMS MongoDB
数据库 数据库
表格 集合
文档
字段
表联合 嵌入文档
主键 主键 (MongoDB 提供了 key 为 _id )

数据库

命名:不能是空字符串。最多64字节,不能包含 / \ . “ * < > : | ? $ \0 空格。

​ 数据库名最后就是文件名。一个数据库一个文件。

集合

不支持预定义,理解成json即可。

数据结构一致性是业务要求,不是数据库要求。

命名:不能包含\0, $, 不能是空字符串,不能以system.开头。

子集合组织数据非常高效。

文档

有特殊的主键_id, 包含时间戳。所以不需要created_time 字段。

key是字符串:不能包含\0, 不能是.和$

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 正则表达式类型。用于存储正则表达式。

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

ObjectId

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

  • 前 4 个字节表示创建 unix 时间戳,格林尼治时间 UTC 时间,比北京时间晚了 8 个小时
  • 接下来的 3 个字节是机器标识码
  • 紧接的两个字节由进程 id 组成 PID
  • 最后三个字节是随机数

img

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

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

> var newObject = ObjectId()
> newObject.getTimestamp()
ISODate("2017-11-25T07:21:10Z")

ObjectId 转为字符串

> newObject.str
5a1919e63df83ce79df8b38f

字符串

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

时间戳

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

  • 前32位是一个 time_t 值(与Unix新纪元相差的秒数)
  • 后32位是在某秒中操作的一个递增的序数

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

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

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

日期

表示当前距离 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)   

索引

索引类型

对mongodb来讲,索引可以创建在collection级别,也可以创建在sub-field中()子collection

完全可以根据自己的需求创建,那么索引可以将随机IO转换为顺序IO

索引类型:

1、单键索引(创建在一个字段上的索引)

2、组合索引

3、多键索引(一个文档中某个字段的值可以是数组,如果创建在这么个字段上,一个字段上有多个值,则为多键索引,(一个值为一个数组))

4、空间索引(只能使用空间索引函数,与mysql一致)

5、文本索引(全文索引)

6、哈希索引

特殊索引

全文索引

2d索引

GridFS

管理

创建

一个集合最多64个索引,建议2个以内。太多的索引会导致增删改 性能降低。

`db.testcoll.ensureIndex({Name:1})` // 普通索引,1 表示升序,-1 表示降序。这个是mysql没有的。Name支持内嵌文档,a.b 还支持数组元素 a.index 对数组建立索引其实是对数组中的每个元素建立索引 `db.testcoll.ensureIndex({Name:1}, {unique:true})` // 唯一索引 `db.testcoll.ensureIndex({Name:1},{sparse:true})` // 稀疏索引, 如果key不存在,默认会存为null, 但是稀疏索引 就不会存。 `db.testcoll.ensureIndex({Name:1,Age:1})` // 组合索引, 索引是有顺序的,类似mysql, 前缀匹配。所以顺序选择很重要。 // 基本方法就是哪个用于排序,哪个放前面。 否则就需要在内存里排序。 // 组合索引中的值 最多有一个是数组。 // 同时,索引的排序方向 和 查询方向也得一致。

参数说明

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设定,设定集合的生存时间。 1分钟清理一次
v index version 索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。
weights document 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。
default_language string 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语
language_override string 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language.

索引创建分前台模式和后台模式:

  • 前台模式: 阻塞读写请求,直到索引建完。没有用户访问时可用。
  • 后台模式:不阻塞读写请求,当有新的读写请求时,暂停索引创建。 比前台模式慢很多。随时可用。

在集群和分片上创建索引要注意,因为创建索引费时,可能会影响业务运行,可选的方案是手动创建。先把成员移出,创建完索引后再加入。

查询

// 索引都会存到 `system.indexes`集合里。 db.coll.getIndexes() // 查看集合的索引。 coll为集合名字。

删除

db.coll.dropIndex(name) // name 可以通过 db.coll.getIndexes() 查看 db.coll.dropIndex("*")// 删除coll上的所有索引

使用

查询时,尽量只返回索引里包含的字段,这样性能更高。(先查索引,再查表。有2次过程,如果查索引就够返回,就可以不查表了)

有些查询无法使用索引,如 $where$exist

$ne 很低效,需要扫描整个索引表。

$not 有时能,如把$lt 改成 $gte , 但更多时候完全不能,只能全表扫描。

$nin 全表扫描,不能使用索引。

使用经验和mysql一样,尽量减小结果集。

索引创建参考

  1. 对需要排序的字段建索引
  2. 注意顺序和方向,保持和查询条件一致
  3. 精确匹配在前,返回匹配在后
  4. 索引基数大的在前,小的在后。(索引基数就是去重后的值的集合大小)

默认会自动选择索引,也可以指定索引

db.testcoll.find({Name: "User19"}).hint({Name:1})

也可以强制不使用索引。

db.testcoll.find({Name: "User19"}).hint({"$natural":1}) // 按磁盘顺序返回

分析

db.users.find({"usernmae":"user101"}).explain(); // explain 查看索引使用情况 db.users.find({"usernmae":"user101"}).explain("executionStats"); // 获取执行时间,扫描行数等。

运维

三种模式

单机

mongod --port 30000 --dbpath /var/lib/db

维护数据库时,只能以单机模式启动。维护完后,再用副本集的方式启动。

副本集

类似于mysql 的主从。也是基于日志的方式做同步。。日志存在local.oplog集合中。

MongoDB复制结构图

副本集特征:

  • N 个节点的集群
  • 任何节点可作为主节点
  • 所有写入操作都在主节点上
  • 自动故障转移
  • 自动恢复
> rs.initiate() // 启动新的副本集 > rs.conf() // 查看副本集的配置 > rs.status() // 查看副本集的状态 > rs.add(HOST_NAME:PORT) // 添加成员。只能在主节点上运行。 > db.isMaster() // 是否是主节点。

建副本集示例(单机测试)

mongo --nodb // shell replicatSet = new ReplSetTest("nodes":3)// 创建测试副本集。3个节点,1主2从 replicatSet.startSet() // 启动3个进程 replicatSet.initiate() // 配置复制功能 // 接下来可以测试。 // 在主节点 写数据。然后 // 1. 在从节点读数据,默认情况下 从节点拒绝读取,抛出错误。 // 2. conn.setSlaveOK() 设置可读 之后再读取数据。 // 3. 测试写 从节点不能写。也会抛出错误。 // 4. 关掉主节点 db.adminCommand({"shutdown":1}) 会发现有从节点升级为主节点了。 replicatSet.stopSet() // 关闭

建副本集示例(线上版本)

// 假设有3个服务器都安装了mongo, server-1, server-2, server-3, sever-1有数据,其他的没有 mongod --replSet spock -f mongod.conf --fork // 在3个服务器启动进程。spock是副本集名称 //在有数据的服务器上server-1 上运行。 > config = { "_id": "spock", // 副本集名称 "members":[ // 成员列表。不能指定谁是主节点,自动选出来的。 {"_id":0, "host":"server-1:27017"}, {"_id":1, "host":"server-2:27017"}, {"_id":2, "host":"server-3:27017"} ] } > db = (new Mongo("server-1:27017")).getDB("test") > rs.initiate(config) // 初始化副本集。只能通过shell 配置副本集 // 在修改副本集时,主节点会断开所有连接,并退化为普通节点。都更新完配置后 重新选举,之后自动恢复连接 > rs.add("server-3:27017") // 添加成员 > rs.remove("server-2:27017")// 删除成员 > rs.config() // 查看当前配置 > rs.reconfig(conf) //重新配置 // 副本集配置文件存在local.system.replSet`集合中。

选举机制

只有大多数(1/2 以上)支持的节点才能成为主节点,当无法选举时,所有节点退化为备份节点。

所以只有2个成员的副本集 是无法做到自动故障转移的,当挂了一个后,另一个无法满足“大多数”要求,还是备份节点。

解决办法是引入“仲裁者”(最多引入一个)

rs.addArb("server-5:27017") // 增加仲裁者方式一,仲裁者只参加选举,不备份数据和提供服务 rs.add({"_id":4, "host":"server-5:27017","arbiterOnly":true}) //增加仲裁者方式二 // 主节点挂了后,如果只有一个可用成员了,仲裁者会转成备份节点,然后复制数据。此时对服务器会产生压力。

推荐配置:

  • 两个数据中心,1多1少。这样 挂了少的数据中心,多的数据中心能选举出主节点,但是多的数据中心挂了,也不能选举出主节点了。
  • 3个数据中心,2个平均分配,另一个放一个成员。平均分配的都有机会成为主节点。

选举时机

当备份节点发现自己无法连接到主节点时,就会通知副本集中的其他成员,希望选举自己成为主节点。

投票

  • 如果自己能连接到主节点,投反对票。
  • 如果候选节点的数据不是最新的,投反对票
  • 如果有更高优先级的节点可用,投发对票。优先级在配置成员时设置{“priority”:1} 0~100, 默认为1, 优先级为0的成员不能成为主节点。

基本上只要有一个成员投了反对票。选举就会取消。

配置副本集成员的选项

  • arbiterOnly true/false 是否为仲裁者
  • _id 唯一编号
  • host 服务进程地址
  • priority 优先级,影响选举
  • hidden true/false 是否隐藏。默认false, 隐藏的成员不提供服务,只是备份数据。
  • slaveDelay 延迟时间,得配合hidden 使用,当有破坏性操作发生时,可以从延迟的数据成员中恢复。要求priority=0
  • buildIndexes: 是否创建索引。默认true, 如果节点仅仅用作备份,而不提供服务,设置为False 可以提高备份速度。要求priority=0

一些维护命令

rs.stepDown(seconds) // 主节点降级为备份节点。seconds 可选,默认60秒 rs.freeze(seconds) // 冻结备份节点,使其无法选举为主节点。seconds=0 为解除冻结 db.adminCommand({"replSetMaintenanceMode":true}) // 强制某个成员进入维护模式,这样就不会接受客户端请求,也不会成为复制源了。在节点上执行耗时操作时,会自动进入维护模式

主从模式

模拟mysql 的主从机制,没有故障自动转移机制。可以指定主节点。

主从模式不会把请求发送给从节点,如果需要读从节点,需要程序指定从节点的链接。

mongd --master // 启动主节点 mongd --slave --source server-0:27017 // 启动从节点,并指定主节点 mongd --slave --source server-0:27017 --only xxx-db // 可以只复制某个数据库

分片

用于单机扛不住压力时,类似于mysql 的 分库。

img

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

  • Shard:

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

  • Config Server:

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

  • Query Routers:

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

备份与恢复

备份

>mongodump -h dbhost -d dbname -o dbdirectory
  • -h:

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

  • -d:

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

  • -o:

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

恢复

mongorestore -h <hostname><:port> -d dbname <path>
  • –host <:port>, -h <:port>:

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

  • –db , -d :

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

  • –drop:

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

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

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

  • –dir:

    指定备份的目录

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

管理

获取当前操作

db.currentOp() // 类似mysql 的 show PROCESSLIST; // 支持过滤,如 db.currentOp({"ns":"prod.users"}) // 限制在prod.users 集合 运行的操作

终止操作

db.killOp(opid) // 杀掉opid 对应的操作

慢查询

db.setProfilingLevel(2) // 设置分析器级别。2-记录所有内容,存到 `system.profile`集合中 db.setProfilingLevel(1, seconds) // 开启慢查询。时间差过seconds 的操作才记录。单位毫秒 db.setProfilingLevel(0) // 关闭分析器,啥也不记录。 但是时间>seconds 操作还是会打日志,只是不写入`system.profile`集合。 db.getProfilingLevel() // 获取分析器配置。

统计

Object.bsonsize(doc) // 查看文档大小。不包括索引和padding, 仅数据大小 db.coll.stats() // 集合的统计信息 db.stats() // 数据库的统计信息

命令行工具

mongostat # 获取运行状态 mongotop # 类似linux的top命令

安全

先添加用户

db.addUser(username, password, readonly) // username: 用户名 password:密码 readonly:是否只读权限。 // 在admin和local 数据库中添加的用户都是管理员用户。管理员用户才能使用 show dbs 等命令。 // 添加的用户实际是存在db.system.users 里。 db.system.users.remove({username:"xxx"}) //删除用户。

重启mongodb, 启用安全检查 --auth

mongod --auth

接下来使用数据库之前得先认证

use test db.auth(username, password)

预热

数据库预热

for file in /data/db/brains.* // brains 为数据库名 do dd if=$file of=/dev/null done

集合预热

db.runCommand({"touch":"logs", "data":true, "index":true}) // touch: 集合名 data: 是否载入数据 index:是否载入索引

shell

帮助

记不住命令不要紧,用下help

help db.help() #查看数据库的帮助 db.mycoll.help() # 查看集合 sh.help() # 查看分片 ... show dbs # show collections # show users # db.foo.update # update 为函数名,可以查看函数的实现

链接

mongo some-host:ip/db

可以连接指定数据库。

也可以通过--nodb 不链接数据库。

这种情况下 得主动链接数据库:

conn = new Mongo("some-host:ip") db = conn.getDB("myDB")

具体的链接schema如下

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的时间。

执行脚本

mongo script.js # 本地执行脚本 mongo --quiet host:ip/db script.js # 指定的数据库执行脚本 #也可以在交互式shell中执行脚本 > load("script.js") # 脚本可以使用db变量和其他全局变量,但是不能用shell中的辅助函数,有专用函数。如下 use foo -> db.getSisterDB("foo") show dbs -> db.getMongo().getDBs() show collections -> db.getCollectionNames()

特殊脚本 .mongorc.js

这个文件会在启动shell 时自动加载。可以定义全局变量,禁用危险操作。

如:

var no = function() { print("Not on my watch."); } db.dropDatabase = DB.prototype.dropDababase = no; // 禁用删数据库命令。

可以定制shell

prompt = function() { return (new Date()) + "> "; }

指定编辑器

EDITOR = "/usr/bin/emacs" // 这样就就可以在shell中 edit 变量,打开编辑器修改完返回shell

导入数据

mongoimport --db hy_web --collection hy_item_fromse --type csv --headerline --ignoreBlanks --file /Users/maynard/Desktop/hy_fromse9.csv

管理

1. showdbs 显示所有的数据库
2. use [db] 切换数据库
3. db 显示当前的数据库或集合
4. db.createCollection("mycoll", {capped:true, size:100000}) 创建capped集合,此集合适合做日志
5. 链接参数 参考 https://www.runoob.com/mongodb/mongodb-connections.html

3个默认数据库

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

创建

1. 创建数据库 `use xxx` 即可 2. 创建集合 `db.createCollection(name, options)` 3. 创建索引 `db.testcoll.ensureIndex({Name:1})` #普通索引 `db.testcoll.ensureIndex({Name:1}, {unique:true})` #唯一索引 `db.testcoll.ensureIndex({Name:1},{sparse:true})` #稀疏索引 `db.testcoll.ensureIndex({Name:1,Age:1},{uniqe:true})` # 组合索引

集合

  • 普通集合不支持预定义,插入式自动建立

  • 固定集合 需要预定义 db.createCollection(name, options)

    • options选项 capped true 为固定集合
    • options选项 size 集合数据的最大大小,单位字节。
    • options选项 max 集合数据的最大条目。 sizemax 谁先触及限制就用谁, 创建就固定了不支持修改。需要修改只能删除重建。
    • autoIndexId 是否自动创建_id 。默认true

    可以通过db.runCommand({"convertToCapped"}) 转成固定集合。但是转不回去。

    固定集合还支持循环游标,只要有数据,游标不释放,类似文件的tail -f, 但10分钟没更新,游标也会释放。

查询

  1. 查询数据库 show dbs;
  2. 查询集合 show tables;show collections;
  3. 查看索引db.testcoll.getIndexes()
  4. 查询数据
    db.col.find(
     query, //可选,使用查询操作符指定查询条件
     projection //可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。
     )
  1. 格式化查询数据 db.col.find().pretty()
db.col.find({"likes": {$gt:50}, $or: [{"by": "菜鸟教程"},{"title": "MongoDB 教程"}]}).pretty()
  1. 判断索引是否用上

    db.testcoll.find({Name: "User19"}).explain()	
    

指定返回的key

参数projection控制。key 为字段,value 为1/0, 1为返回,0为不返回。

条件说明

基本上:条件语句是内层文档的键,而修改器则是外层文档的键。

有一些 元操作符 也位于外层文档中,如$and,$or $nor

精确匹配AND

最简单的形式,就是个document.

如:

db.users.find({"name":"joe", "email":"joe@123.com"}) // = where name="joe" and email="joe@123.com"

复杂点的用$and

db.users.find({"$and":[{"x":{"lt":1},{"x":4}}]}) // = where x<1 and x=4

范围查询 > >= < <= !=

分别对应 $gt$gte$lt$lte $ne

示例:

db.users.find({"age": {"$gte": 18, "$lte":30}}) // = where age >=18 and age <=30

这里有个注意事项,当对应的值是数组时,如果数组中的任意元素能匹配其中一个条件,也会返回。

如:

{"x":5} // 1 {"x":15} // 2 {"x":25} //3 {"x":[5,25]} // 4 // 查询 x > 10 and x < 20 的文档 db.test.find({"x":{"$gt":10, "$lt":20}}) // 不光返回了第二个,第四个也返回了,因为5<20, 25>10 都满足了一个条件。

针对数组,可以使用$elemMatch 要求匹配所有提交,而不是任意一个条件。

db.test.find({"x":{"$elemMatch":{"$gt":10, "$lt":20}}}) // 没有返回。第2个不是数组也不能返回了。

最佳的方式其实是建立索引,不光解决问题,效率还更高。

db.test.find({"x":{"$gt":10, "$lt":20}}).min({"x":10}).max({"x":20})

or查询

两种形式,1种是单个字段的。用$in, 多个字段的用$or

$in示例

db.raffle.find({"ticket_no":{"$in":[725, 542,390]}}) // $nin 表示 not in // = where ticket_no in [725,542,390]

$or 示例

db.raffle.find({"$or":["ticket_no":{"$in":[725,542,390], "winner":true}]}) // 数组里的元素就是某个key 的条件,然后or // = where ticket_no in [725,542,390] or winner = true

$or 其实是多次查询,然后合并结果集 去重。所以能用$in 就不要用$or

条件取反 $not

和其他条件组合。。加上$not 就是取反。

特定类型的查询

null

null 不光匹配 key=null 的文档,还会匹配 key 不存在的文档。

所以如果要精确匹配key=null, 得配合使用$exists

db.c.find({"z":{"$in":[null], "$exists":true}})

正则表达式

示例:

db.users.find({"name":/joe/i}) // 就是js正则 // mongo 支持对前缀正则表达式建立索引

查询数组

匹配一个元素

默认像$in的实现。譬如有个数据{"fruit":["a","b","c"]}

通过db.food.find({"fruit":"a"}) 是可以查询到的。

匹配多个元素得用$all

db.food.find({"fruit":{"$all":["a","b"]}}) // 数组里有a 和 b 的都会返回,与a,b 的顺序无关

精准匹配

直接用数组精准匹配,得完全一致才会返回。

db.food.find({"fruit":["a","b"]}) // ["b","a"] , ["a","b","c"] 都不会返回。

匹配具体的index 的值

db.food.find({"fruit.2":"c"}) // 返回第3个元素是 c 的文档。

匹配元素个数=number 的文档

db.food.find({"fruit":{"$size":3}}) // 查询fruit数组长度=3的文档,不能与其他查询条件组合使用。

返回数组的部分内容。类似limit,offset

// 出现在projection 中。 默认返回所有key db.blog.posts.findOne(criteria, {"comments":{"$slice":10}}) // 返回前10条评论 db.blog.posts.findOne(criteria, {"comments":{"$slice":-10}}) // 返回后10条评论 db.blog.posts.findOne(criteria, {"comments":{"$slice":[20,10]}}) // 返回20~30 之间的评论。

返回匹配的第一个元素

db.blog.posts.findOne({"comments.name":"bob"}, {"comments.$":1}) // 返回前10条评论

查询内嵌文档

有2种方式:

  • 1 完整的文档,这种是精确匹配

    //name 是个内嵌文档{"name":{"first":"Joe","last":"Schmoe"}} db.people.find({"name":{"first":"Joe","last":"Schmoe"}}) // 顺序错了,或者有更多的元素都不能匹配。
  • 2 通过.语法,这种更常用,更灵活。

    db.people.find({"name.first":"Joe", "name.last":"Schmoe"}) // = where name.first = "Joe" and name.last = "schmoe"

当内嵌文档是数组元素时,和范围查询中提到的一样,会尽量匹配多个元素。。只要满足其中一个条件就能返回。如果要满足每个条件,同样需要用$elemMatch

$where

万能的$where ,你提供函数, 返回true就返回文档。性能极低。应避免使用。

模糊匹配

查询 title 包含"教"字的文档:

db.col.find({title:/教/})
查询 title 字段以"教"字开头的文档:

db.col.find({title:/^教/})
查询 titl e字段以"教"字结尾的文档:

db.col.find({title:/教$/})

按数据类型查询

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

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

游标

find() 并不查询数据库,而是反而一个游标。此时你可以额外加些选项,如skip, limit, sort 等。

当调用 hasNext(), next()时才真正发往数据库。一次返回100条记录/4M数据,取小值。

游标默认只有10分钟有效期,过期会销毁,如果需要更长的时间, 可以查看immortal函数。

分页查询

db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER) // 类比limit, offset

这里有个问题。skip 即时略过文档,数据库还要查询,当skip的数量很多时,性能会非常低。

避免使用skip的技巧是 增加查询提交。譬如按价格排序。在获取第一页后,用最后一条数据的价格 作为条件 重新查询第一页 其实就是相当于第二页了。

随机查询的问题。。技巧是给文档增加一个随机值。然后查询时 db.foo.findOne("random":{"$gt": random}) 这样也能避免skip大量数据。

排序

db.COLLECTION_NAME.find().sort({KEY:1}) // 1为升序。-1为降序。

聚合

aggregate

示例

// 结构 { _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 }, // select by_user, count(*) from mycol group by by_user 在mongo中的实现 db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : 1}}}])

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

表达式 描述 实例
$sum 计算总和。 db.mycol.aggregate([{Extra open brace or missing close braceby_user", num_tutorial : {likes"}}}])
$avg 计算平均值 db.mycol.aggregate([{Extra open brace or missing close braceby_user", num_tutorial : {likes"}}}])
$min 获取集合中所有文档对应值得最小值。 db.mycol.aggregate([{Extra open brace or missing close braceby_user", num_tutorial : {likes"}}}])
$max 获取集合中所有文档对应值得最大值。 db.mycol.aggregate([{Extra open brace or missing close braceby_user", num_tutorial : {likes"}}}])
$push 将值加入一个数组中,不会判断是否有重复的值。 db.mycol.aggregate([{Extra open brace or missing close braceby_user", url : {url"}}}])
$addToSet 将值加入一个数组中,会判断是否有重复的值,若相同的值在数组中已经存在了,则不加入。 db.mycol.aggregate([{Extra open brace or missing close braceby_user", url : {url"}}}])
$first 根据资源文档的排序获取第一个文档数据。 db.mycol.aggregate([{Extra open brace or missing close braceby_user", first_url : {url"}}}])
$last 根据资源文档的排序获取最后一个文档数据 db.mycol.aggregate([{Extra open brace or missing close braceby_user", last_url : {url"}}}])

pipeline

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

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

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

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

示例

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

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

内置的聚合命令

count

db.foo.count() // 统计集合条目数

distinct 获取值的set

db.runCommand({"distinct": "people", "key":"age"}) // people 是集合名, age 是key名字

插入

db.COLLECTION_NAME.insert(document)//已存在报错,document 可以是对象也可以是数组,如果是数组就是批量插入。批量插入时遇到错误停止,如果想继续,使用continueOnError 选项。 db.COLLECTION_NAME.save(document)//不存在插入,存在更新 <3.2 // >=3.2 语义更明确,插入一个 db.collection.insertOne( <document>, { writeConcern: <document> } ) // >=3.2 插入多个 // 参数说明: // document:要写入的文档。 // writeConcern:写入策略,默认为 1,即要求确认写操作,0 是不要求。 // ordered:指定是否按顺序写入,默认 true,按顺序写入。 db.collection.insertMany( [ <document 1> , <document 2>, ... ], { writeConcern: <document>, ordered: <boolean> } ) db.collection.replaceOne() >=3.2

插入校验

  1. 是否有_id字段,没有则生成。这个客户端查询就可以生成。没必要使用服务器资源。
  2. 文档大小是否超过16M, 最大是16M.查看大小Object.bsonsize(doc)

$setOnInsert 的作用,只有插入数据时才设置字段,以后就不能修改了。

更新

文档替换

1.条件更新

db.collection.update( <query>, //update的查询条件,类似sql update查询内where后面的。 <update>,//update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql update查询内set后面的 { upsert: <boolean>,//可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。 multi: <boolean>,//可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。 writeConcern: <document> //可选,抛出异常的级别。 } ) // 有个注意事项。得确保query 查出来只有一个文档,否则会更新失败。正常的办法是,先根据有意义的业务字段 query, 修改完后,再用`_id`作为query条件。
  1. 整体替换
db.collection.save( <document>, { writeConcern: <document> } )

options

upsert

不存在就新建,存在就更新。是个原子操作。

db.users.update({"rep":25},{"$inc":"rep":3},true)
multi

默认false, 只更新匹配的第一个文档。设置为true, 更新所有文档。

如果需要获知更新了多个个文档。

可以通过db.runCommand({getLastError:1}) 获取。返回结果中的`n表示影响行数。

原子更新 findAndModify

先查,修改,保存,这个不是原子的。

findAndModify 支持原子操作。

  • findAndModify 字符串,集合名。
  • query 查询文档,where 条件
  • sort 排序
  • update 修改文档
  • remove 是否删除。默认false, 用于原子删除。 update/remove 二选1. 要么更新,要么删除。
  • new 表示返回修改前的文档还是修改后的文档。默认返回修改前的文档。
  • fields 文档中需要返回的字段。可选
  • upsert 默认false

示例:

ps = db.runCommand({"findAndModify":"processes", "query":{"status":"READY"}, "sort":{"priority":-1}, "update":{"$set":{"status":"RUNNING"}} }).value

修改器

$set 修改

修改某个字段。支持内嵌文档,当key不存在时,就创建。相对应的$unset 是删除某个key。

db.blog.posts.update({"author.name":"joe"},{"$set":{"author.name":"joe schmoe"}})

$inc 处理数字

增加或减少number,当key不存在时,就创建。 number 必须是数字,对应的value 必须是整形/长整形/双精度浮点型的值。

db.games.update({"game":"pinball","user":"joe"},{"$inc":{"score":50}})

数组修改器

追加一个

$push没有就新建,有就追加元素。

db.blog.posts.update({"title":"A blog post"}, {"$push":{"comments":{"name":"joe","email":"joe@ex.com","content":"nice post."}}})
追加多个

$push + $each 追加多个

db.stock.ticker.update({"_id":"GOOD"}, {"$push":{"hourly":{"$each":[2,3,2]}}})

遍历$each, 调用$push等价于把数组的内容都追加到数组中

限定数组长度

$slice 必须是负数

db.movies.update({"genre":"horror"},{"$push":{"top10":{ "$each":["Nighmare on Elm Street", "Saw"], "$slice":-10}}}) // 只保留最新的10个
排序

只要向数组中添加子对象就需要清理。配合$sort使用

db.movies.find({"{genre":"horror"},{ "$push":{ "top10":{ "$each":[{"name":"a","rating":6.6},{"name":"b","rating":4.3}], "$slice":-10, "$sort":{"rating":-1} } } }) // 实测 报错。应该是版本关系。4.4.10 报错
避免重复

要把数组当成set使用,有两种方式。

第一种$ne配合$push 可以理解成 通过query条件限定 来避免重复。

db.users.update({"authors cited":{"$ne":"Richie"}}, {"$push":{ "authors cited":"Richie" }})

第二种是 $addToSet 推荐使用

db.users.update({ "_id" : ObjectId("619c54203394a36c89266b84")},{"$addToSet":{"emails":"joe@a.com"}})

配和$each可以追加多个。类似$push+ $each

删除元素

从尾删 {"$pop":{"key":1}}

从头删 {"$pop":{“key”:-1}}

$pull根据元素值删除,注意,匹配到的元素值都会删掉。

db.lists.update({},{"$pull":{"todo":"laundry"}})
修改元素值

2种方式。第一种是基于index修改。如:

db.blogs.update({"post",post_id},{"$inc": {"comments.0.votes":1}})

第二种 先查出来再修改,不知道具体的位置, 使用$定位符

db.blogs.update({"comments.author":"John"},{"$set":{ "comments.$.author":"Jim" }})
更新速度

文档是顺序存储的,当更新中间的数据

  1. 当修改的数据比原来要大,存不下时,就需要移动文档。同时调整增长填充因子 这时性能会比较低。
  2. 当修改的数据 不大于原值时,原地修改,性能高。小于原值时,会产生碎片。

由于这个机制的存在,下面的代码可能产生问题

cursor = db.foo.find() while (cursor.hasNext()) { var doc = cursor.next(); doc = process(doc); db.foo.save(doc); // 当doc 在原位置存不下时,会放到后面,然后游标又会再次访问。。。解决办法是 把db.foo.find() 改为 // db.foo.find().snapshot(), 性能会降低,但会实现功能。在批量处理数据时,最好是使用snapshot() }

写入安全机制

写入安全机制,客户端提供实现。写入/更新/删除 都有。

2种机制

  • 应答式写入: 等待数据库响应(写入是否成功),然后才继续下一步。遇到错误会抛异常。
  • 非应答式写入:不返回任何响应。

删除

  1. 删除数据库 db.dropDatabase();
  2. 删除集合 db.collection.drop()
  3. 删除索引 db.testcoll.dropIndex({Name:1})
  4. 删除文档(<2.6)
db.collection.remove( <query>, <justOne> )

删除文档(>=2.6)

// query :(可选)删除的文档的条件。 // justOne : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。 // writeConcern :(可选)抛出异常的级别。 db.collection.remove( <query>, { justOne: <boolean>, writeConcern: <document> } )

如果为空,则删除所有文档,不过会保留集合和集合的元信息。如果要清空所有文档,不如直接删除集合,再重建, 效率高很多。

  1. 现在官方推荐使用 deleteOne() 和 deleteMany() 方法。

本文链接:http://blog.go2live.cn/post/mongodb.html

-- EOF --