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
- 最后三个字节是随机数
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一样,尽量减小结果集。
索引创建参考
- 对需要排序的字段建索引
- 注意顺序和方向,保持和查询条件一致
- 精确匹配在前,返回匹配在后
- 索引基数大的在前,小的在后。(索引基数就是去重后的值的集合大小)
默认会自动选择索引,也可以指定索引
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
集合中。
副本集特征:
- 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 的 分库。
上图中主要有如下所述三个主要组件:
-
Shard:
用于存储实际的数据块,实际生产环境中一个shard server角色可由几台机器组个一个replica set承担,防止主机单点故障
-
Config Server:
mongod实例,存储了整个 ClusterMetadata,其中包括 chunk信息。
-
Query Routers:
前端路由,客户端由此接入,且让整个集群看上去像单一数据库,前端应用可以透明使用。
备份与恢复
备份
>mongodump -h dbhost --port 端口 -u 用户名 -p 密码 -d dbname -o dbdirectory
-
-h:
MongoDB 所在服务器地址,例如:127.0.0.1,当然也可以指定端口号:127.0.0.1:27017
-
–port: 指定端口
-
-u: 用户名
-
-p: 密码
-
-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:
恢复的时候,先删除当前数据,然后恢复备份的数据。就是说,恢复后,备份后添加修改的数据都会被删除,慎用哦!
-
<path>
:mongorestore 最后的一个参数,设置备份数据所在位置,例如:
c:\data\dump\test
。你不能同时指定
<path>
和--dir
选项,--dir
也可以设置备份目录。 -
--dir
:指定备份的目录
你不能同时指定
<path>
和--dir
选项。
管理
获取当前操作
db.currentOp() // 类似mysql 的 show PROCESSLIST;
// 支持过滤,如
db.currentOp({"ns":"prod.users"}) // 限制在prod.users 集合 运行的操作
终止操作
db.killOp(opid) // 杀掉opid 对应的操作
慢查询
慢查询日志一般作为优化步骤里的第一步。通过慢查询日志,定位每一条语句的查询时间。比如超过了200ms,那么查询超过200ms的语句需要优化。然后它通过 .explain() 解析影响行数是不是过大,所以导致查询语句超过200ms。
所以优化步骤一般就是:
- 用慢查询日志(system.profile)找到超过200ms的语句
2 .然后再通过.explain()解析影响行数,分析为什么超过200ms
- 决定是不是需要添加索引
db.setProfilingLevel(2) // 设置分析器级别。2-记录所有内容,存到 `system.profile`集合中
db.setProfilingLevel(1, seconds) // 开启慢查询。时间差过seconds 的操作才记录。单位毫秒
db.setProfilingLevel(0) // 关闭分析器,啥也不记录。 但是时间>seconds 操作还是会打日志,只是不写入`system.profile`集合。
db.getProfilingLevel() // 获取分析器配置。
db.getProfilingStatus() // 查询级别和时间
控制慢查询日志的大小
PRIMARY> db.setProfilingLevel(0) // 关闭Profiling
{ ` `"was"` `: 0, ` `"slowms"` `: 200, ` `"ok"` `: 1 }
PRIMARY> db.system.profile.drop() // 删除system.profile集合
true
PRIMARY> db.createCollection( "system.profile", { capped: true, size:4000000 } ) // 创建一个新的system.profile集合 --- 4M
{ ` `"ok"` `: 1 }
PRIMARY> db.setProfilingLevel(1) // 重新开启Profiling
{ ` `"was"` `: 0, ` `"slowms"` `: 200, ` `"ok"` `: 1 }
查看日志
db.system.profile.find().pretty() // 查看当前所有的慢查询日志
{
"op" : "query", // 操作类型,有insert、query、update、remove、getmore、command
"ns" : "onroad.route_model", // 操作的集合
"query" : {
"$query" : {
"user_id" : 314436841,
"data_time" : {
"$gte" : 1436198400
}
},
"$orderby" : {
"data_time" : 1
}
},
"ntoskip" : 0, // 指定跳过skip()方法 的文档的数量。
"nscanned" : 2, // 为了执行该操作,MongoDB在 index 中浏览的文档数。 一般来说,如果 nscanned 值高于 nreturned 的值,说明数据库为了找到目标文档扫描了很多文档。这时可以考虑创建索引来提高效率。
"nscannedObjects" : 1, // 为了执行该操作,MongoDB在 collection中浏览的文档数。
"keyUpdates" : 0, // 索引更新的数量,改变一个索引键带有一个小的性能开销,因为数据库必须删除旧的key,并插入一个新的key到B-树索引
"numYield" : 1, // 该操作为了使其他操作完成而放弃的次数。通常来说,当他们需要访问还没有完全读入内存中的数据时,操作将放弃。这使得在MongoDB为了放弃操作进行数据读取的同时,还有数据在内存中的其他操作可以完成
"lockStats" : { // 锁信息,R:全局读锁;W:全局写锁;r:特定数据库的读锁;w:特定数据库的写锁
"timeLockedMicros" : { // 该操作获取一个级锁花费的时间。对于请求多个锁的操作,比如对 local 数据库锁来更新 oplog ,该值比该操作的总长要长(即 millis )
"r" : NumberLong(1089485),
"w" : NumberLong(0)
},
"timeAcquiringMicros" : { // 该操作等待获取一个级锁花费的时间。
"r" : NumberLong(102),
"w" : NumberLong(2)
}
},
"nreturned" : 1, // 返回的文档数量
"responseLength" : 1669, // 返回字节长度,如果这个数字很大,考虑值返回所需字段
"millis" : 544, // 消耗的时间(毫秒)
"execStats" : { // 一个文档,其中包含执行 查询 的操作,对于其他操作,这个值是一个空文件, system.profile.execStats 显示了就像树一样的统计结构,每个节点提供了在执行阶段的查询操作情况。
"type" : "LIMIT", // 使用limit限制返回数
"works" : 2,
"yields" : 1,
"unyields" : 1,
"invalidates" : 0,
"advanced" : 1,
"needTime" : 0,
"needFetch" : 0,
"isEOF" : 1, // 是否为文件结束符
"children" : [
{
"type" : "FETCH", // 根据索引去检索指定document
"works" : 1,
"yields" : 1,
"unyields" : 1,
"invalidates" : 0,
"advanced" : 1,
"needTime" : 0,
"needFetch" : 0,
"isEOF" : 0,
"alreadyHasObj" : 0,
"forcedFetches" : 0,
"matchTested" : 0,
"children" : [
{
"type" : "IXSCAN", // 扫描索引键
"works" : 1,
"yields" : 1,
"unyields" : 1,
"invalidates" : 0,
"advanced" : 1,
"needTime" : 0,
"needFetch" : 0,
"isEOF" : 0,
"keyPattern" : "{ user_id: 1.0, data_time: -1.0 }",
"boundsVerbose" : "field #0['user_id']: [314436841, 314436841], field #1['data_time']: [1436198400, inf.0]",
"isMultiKey" : 0,
"yieldMovedCursor" : 0,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0,
"matchTested" : 0,
"keysExamined" : 2,
"children" : [ ]
}
]
}
]
},
"ts" : ISODate("2015-10-15T07:41:03.061Z"), // 该命令在何时执行
"client" : "10.10.86.171", // 链接ip或则主机
"allUsers" : [
{
"user" : "martin_v8",
"db" : "onroad"
}
],
"user" : "martin_v8@onroad"
}
统计
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
集合数据的最大条目。size
和max
谁先触及限制就用谁, 创建就固定了不支持修改。需要修改只能删除重建。 autoIndexId
是否自动创建_id
。默认true
可以通过
db.runCommand({"convertToCapped"})
转成固定集合。但是转不回去。固定集合还支持循环游标,只要有数据,游标不释放,类似文件的
tail -f
, 但10分钟没更新,游标也会释放。 - options选项
查询
- 查询数据库
show dbs
; - 查询集合
show tables;show collections
; - 查看索引
db.testcoll.getIndexes()
- 查询数据
db.col.find(
query, //可选,使用查询操作符指定查询条件
projection //可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。
)
- 格式化查询数据
db.col.find().pretty()
db.col.find({"likes": {$gt:50}, $or: [{"by": "菜鸟教程"},{"title": "MongoDB 教程"}]}).pretty()
-
判断索引是否用上
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([{KaTeX parse error: Expected '}', got 'EOF' at end of input: …roup : {_id : "by_user", num_tutorial : { s u m : " sum : " sum:"likes"}}}]) |
$avg | 计算平均值 | db.mycol.aggregate([{KaTeX parse error: Expected '}', got 'EOF' at end of input: …roup : {_id : "by_user", num_tutorial : { a v g : " avg : " avg:"likes"}}}]) |
$min | 获取集合中所有文档对应值得最小值。 | db.mycol.aggregate([{KaTeX parse error: Expected '}', got 'EOF' at end of input: …roup : {_id : "by_user", num_tutorial : { m i n : " min : " min:"likes"}}}]) |
$max | 获取集合中所有文档对应值得最大值。 | db.mycol.aggregate([{KaTeX parse error: Expected '}', got 'EOF' at end of input: …roup : {_id : "by_user", num_tutorial : { m a x : " max : " max:"likes"}}}]) |
$push | 将值加入一个数组中,不会判断是否有重复的值。 | db.mycol.aggregate([{KaTeX parse error: Expected '}', got 'EOF' at end of input: …roup : {_id : "by_user", url : { p u s h : " push: " push:"url"}}}]) |
$addToSet | 将值加入一个数组中,会判断是否有重复的值,若相同的值在数组中已经存在了,则不加入。 | db.mycol.aggregate([{KaTeX parse error: Expected '}', got 'EOF' at end of input: …roup : {_id : "by_user", url : { a d d T o S e t : " addToSet : " addToSet:"url"}}}]) |
$first | 根据资源文档的排序获取第一个文档数据。 | db.mycol.aggregate([{KaTeX parse error: Expected '}', got 'EOF' at end of input: …roup : {_id : "by_user", first_url : { f i r s t : " first : " first:"url"}}}]) |
$last | 根据资源文档的排序获取最后一个文档数据 | db.mycol.aggregate([{KaTeX parse error: Expected '}', got 'EOF' at end of input: …roup : {_id : "by_user", last_url : { l a s t : " last : " last:"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
插入校验
- 是否有
_id
字段,没有则生成。这个客户端查询就可以生成。没必要使用服务器资源。 - 文档大小是否超过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条件。
- 整体替换
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"
}})
更新速度
文档是顺序存储的,当更新中间的数据
- 当修改的数据比原来要大,存不下时,就需要移动文档。同时调整增长填充因子 这时性能会比较低。
- 当修改的数据 不大于原值时,原地修改,性能高。小于原值时,会产生碎片。
由于这个机制的存在,下面的代码可能产生问题
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种机制
- 应答式写入: 等待数据库响应(写入是否成功),然后才继续下一步。遇到错误会抛异常。
- 非应答式写入:不返回任何响应。
删除
- 删除数据库
db.dropDatabase();
- 删除集合
db.collection.drop()
- 删除索引
db.testcoll.dropIndex({Name:1})
- 删除文档(<2.6)
db.collection.remove(
<query>,
<justOne>
)
删除文档(>=2.6)
// query :(可选)删除的文档的条件。
// justOne : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。
// writeConcern :(可选)抛出异常的级别。
db.collection.remove(
<query>,
{
justOne: <boolean>,
writeConcern: <document>
}
)
如果为空,则删除所有文档,不过会保留集合和集合的元信息。如果要清空所有文档,不如直接删除集合,再重建, 效率高很多。
- 现在官方推荐使用 deleteOne() 和 deleteMany() 方法。