MongoDB学习笔记

MongoDB 是由 C++ 语言编写的,是一个基于分布式文件存储的开源数据库系统。在高负载的情况下,添加更多的节点,可以保证服务器性能。
MongoDB 旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。
MongoDB 将数据存储为一个文档,数据结构由键值 (key => value ) 对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。
作为 NoSQL 里的著名项目,它比其他 NoSQL 数据库的优势之一是它强大的、基于文档的查询语言,由于查询非常容易转换(将 SQL 语句转换成 MongoDB 查询函数调用),这使得从关系数据库到 MongoDB 的过渡变得简单,并且官方提供了完善的驱动支持,它的目的就是替代传统的 SQL 数据库。

特性

  • 无数据结构限制
    mongo 没有”表”的概念,也不用设计表。
    它使用”集合”存储 多个”键值对”,取代表的功能;想像一下 JSON 这种数据类型。
  • 支持完全索引
  • 支持复制和故障恢复
  • 快速、高扩展性(分片扩展)
  • mongo有数据库的概念,但可以不经创建,直接使用。

当然它是不保证实时一致性,并且不支持事务的,绝大多数的 NoSQL 好像都是不支持的,带来的好处就是快!更好的性能!
mongo 没有mysql中”记录”的概念,mongo使用”文档”存储任意数量的”键值对”信息;
mongo 无需手动设置”主键”,系统会自动为每一个”文档”自动添加”_id”键值对,保证数据的唯一性.

mysqlmongodb
表(table)集合(collection)
记录(row)文档(document)
主键(primary key) 手动设置_id 自动生成

然后再来说说以前写过的 Redis,同样是 NoSQL ,区别在那?具体的就不展开了,说主要的:Redis 是内存型数据库,常用于做缓存;MongoDB 是持久化存储的也就是存硬盘的,易用性不错,也更加的灵活,毕竟目标是取代 SQL 数据库的,性能也不俗,这或许是 NoSQL 的一大特点。
Redis 最大的优点就是快,快,还 TMD 是快!

MongoDB 为什么比 MySQL 快?
写操作 MongoDB 比传统数据库快的根本原因是 Mongo 使用的内存映射技术 :
写入数据时候只要在内存里完成就可以返回给应用程序,这样并发量自然就很高。而保存到硬体的操作则在后台异步完成。
读操作MongoDB快的原因是:
1)MongoDB 的设计要求你常用的数据(working set) 可以在内存里装下。这样大部分操作只需要读内存,自然很快。
2)文档性模式设计一般会是的你所需要的数据都相对集中在一起(内存或硬盘),大家知道硬盘读写耗时最多是随机读写所产生的磁头定位时间,数据集中在一起则减少了关系性数据库需要从各个地方去把数据找过来(然后Join)所耗费的随机读时间。
再有就是分布式集群的水平扩展所带来的压力分担。

安装

我是在 Linux 上做测试,所以就简单说说 linux 下的安装,直接从官网下载二进制包,使用 tar -zxvf 解压,移到 /usr/local 即可。
接下来你可以配置下环境变量(比如编辑当前用户目录下的 .bash_profile 文件的方式),然后使用 mongod 命令启动 mongodb 的服务。
执行 ./bin/mongod 服务默认会在前台执行,如果数据目录不是 /data/db 那么可以手动指定目录,启动命令为:./bin/mongod --dbpath=/usr/mongo_data MongoDB 数据库服务的默认端口是 27017 .
无论指定目录还是使用默认目录都需要手动进行创建。

其实还可以进行下简单的配置:
进入到 bin 目录下,编辑 mongodb.conf 文件,内容如下:

1
2
3
4
5
6
7
8
9
10
dbpath = /data/db #数据文件存放目录
logpath = /data/logs/mongodb.log #日志文件存放目录
port = 27017 #端口
fork = true #以守护程序的方式启用,即在后台运行

# 通过访问http://IP:28017/可以查看到mongodb启动的一些信息,同时也对mongodb运行
# 的统计情况进行监控。在使用mongodb过程中,我们可以使用参数将该功能禁用掉。
# 修改配置文件 mongodb.conf,增加参数选项:nohttpinterface = true 即可。
# 3.6+ 版本中已经被删除
nohttpinterface = true

执行./mongod -f mongodb.conf命令表示启动 MongoDB.

相关配置说明:
–dbpath 数据库路径(数据文件)
–logpath 日志文件路径
–master 指为主机器
–slave 指定为从机器
–source 指定主机器的IP地址
–pologSize 指定日志文件大小不超过 64M. 因为 resync 是非常操作量大且耗时,最好通过设置一个足够大的oplogSize来避免resync(默认的 oplog大小是空闲磁盘大小的5%)。
–logappend 日志文件末尾添定加
–port 启用端口号
–fork 在后台运行
–only 指定只复制哪一个数据库
–slavedelay 指从复制检测的时间间隔
–auth 是否需要验证权限登录(用户名和密码)

测试

执行mongo命令表示表示进入到 MongDB 的控制台,进入到控制台之后,我们输入db.version()命令,如果能显示出当前 MongoDB 的版本号,说明安装成功了。
默认情况下,连接地址是 127.0.0.1:27017,连接的数据库是 test 数据库,我们也可以手动指定连接地址和连接的数据库:
mongo 127.0.0.1:27017/admin
如果是 3.0+ 的版本,可能会提示下面这样的错误:

WARNING: /sys/kernel/mm/transparent_hugepage/enabled is ‘always’.
We suggest setting it to ‘never’
WARNING: /sys/kernel/mm/transparent_hugepage/defrag is ‘always’.
We suggest setting it to ‘never’

解决方案:
执行下面的两句命令:

1
2
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

弊端是重启后会失效,所以可以加入到开机启动里面,编辑 /etc/rc.local 追加下面的代码:

1
2
3
4
5
6
if test -f /sys/kernel/mm/transparent_hugepage/enabled; then
echo never > /sys/kernel/mm/transparent_hugepage/enabled
fi
if test -f /sys/kernel/mm/transparent_hugepage/defrag; then
echo never > /sys/kernel/mm/transparent_hugepage/defrag
fi

这样就一劳永逸了,那么这个警告的原因是什么?
原因就是 HDFS 会因为这个性能严重受影响。设置以后就是允许 hugepage 可以动态分配,而不是系统启动时预先分配,看上去对内存消耗很大的服务都不喜欢它。感觉这是一个 lazy loading 的设计思想。

关闭服务

在客户端里使用 db.shutdownServer() 命令可以关闭到 MongoDB 服务,但是这个命令的执行要在 admin 数据库下,所以先切换到 admin (use admin)

基本操作

查看所有数据库:show dbs
删除当前数据库:db.dropDatabase()
PS:默认是不需要手动创建数据库的,mongodb 会自动根据需要来创建。
查看当前使用的数据库:db.getName()
查看当前数据库中的表(集合):show tables or show collections

插入

在 MongoDB 中,数据以集合的形式存储。如果需要,您可以分割文档。下面创建一个文档并把它存储到一个名为 “colors” 的新集合中(Json 格式):
db.colors.save({name:"red",value:"FF0000"});
通过查询数据库来验证文档已保存( findOne 可以返回一条数据):
db.colors.find();
默认会查出所有的记录。MongoDB 中的文档以 BSON(二进制 JSON)形式存储。
因为 Mongodb 支持 Js 语法,所以可以使用 for 来批量插入:
for(i=1;i<=10;i++)db.demo.insert({index:i});
还可以进行计数操作:
db.demo.find().count();
使用 skip、limit、sort 操作(1:升序;-1:降序):
db.demo.find().skip(2).limit(3).sort({index:1})
上面插入数据使用了两种方式,一种是 save,另一种是 insert,它们的区别是:
insert:当主键 “_id” 在集合中存在时,不做任何处理。
save:当主键 “_id” 在集合中存在时,进行更新。

更新

更新数据同样使用的是 update 关键字:
db.demo.update({index:1},{index:100})
如果只需要更新部分字段,那么就需要使用 set 操作符:
db.colors.update({name:"red"},{$set:{value:"red"}})
当记录不存在时,插入一条数据:
db.demo.update({index:1},{index:100},true)
有时候查询会查到多条数据,默认只会更新第一条,如果需要批量更新那么可以使用:
db.colors.update({name:"red"},{$set:{value:"red"}},false,true)
和第二条类似,批量更新只支持 set 方式,第三个参数就是不存在时是否创建,第四个就是是否更新全部数据(默认false)

删除

与更新不同,删除默认会删除所有查到的数据,关键字为 remove ,参数不允许为空:
db.demo.remove({index:100})
删除表(集合)使用的是 drop 关键字:
db.demo.drop()

查找

find 已经用过了,很简单,默认查出所有的数据,或者你需要一条用 findOne,配合 skip 和 limit 是非常有用的。
如果是查找单条数据可以 find({x:1}) ,就是说如果你记得其中的属性的话,配合索引速度会更快。
或者根据某个属性是否存在来查询:find({m:{$exists:false}})
强制使用索引查询:find({m:{$exists:false}}).hint("name")
更多强大的查找功能参考拓展里,如果我用到或许会来进行补充。

索引

索引的作用就是为了加快查询速度,这个都是一样的,3.0+ 的版本创建索引使用 createIndex,查看索引使用 getIndexes。
db.demo.createIndex({index:1})
和排序类似,1 代表正向,-1 代表反向。
PS:索引可以重复创建,不会报错,第二次会直接返回成功。
上面通过 createIndex 给 index 创建了索引(单键索引),如果我们插入的数据是:{index:[1,2,3,4]} 这样的形式,那么系统就会自动创建一个多键索引,并不需要显示的创建。

MongoDB automatically creates a multikey index if any indexed field is an array; you do not need to explicitly specify the multikey type.

如果要根据多个字段来创建索引那就是所谓的复合索引了:
db.demo.createIndex({index:1,name:1})
删除索引可以通过 dropIndex(name) 的形式,name 指的是索引名,可以通过 getIndexes() 获得。

过期索引

这个就非常有用了,比如可以存用户的登陆信息、日志等,因为它的特性是:超过设定的时间后索引被删除,同时相应的数据也会被清除。
创建也非常的简单,只需要加一个时间参数:
db.demo.createIndex({time:1},{expireAfterSeconds:10})
这样数据 10 秒后就会被删除。
db.demo.insert({time:new Date()})
以这个例子来说,time 字段必须是 ISODate 或者 ISODate 数组(按照最小的时间进行删除),不能使用时间戳,否则不能被自动删除。
过期索引不能是复合索引,因为无法根据两个日期来进行删除。
删除的时间是不精确的,删除过程是由后台程序每 60s 跑一次,而且删除也需要一些时间,所以存在误差。

全文索引

看名字应该就知道是什么意思了,主要用在搜索上,根据某些关键字就能搜出相应的数据,创建全文索引的方式有下面几种:

1
2
3
4
5
6
7
8
# key-字段名,value-固定字符串text
db.articles.createIndex({key:"text"})

# 在多个字段上创建全文索引
db.articles.createIndex({key1:"text",key2:"text"})

# 给所有字段创建全文索引
db.articles.createIndex({"$**":"text"})

然后就是使用了,根据建立的全文索引来查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查询包含 coffee 的内容的文档
db.article.find({$text:{$search:"coffee"}})

#(或查询)查询包含 aa 或 bb 或 cc 的内容的文档
db.article.find({$text:{$search:"aa bb cc"}})

# -为排除包含有 cc 内容的文档
db.article.find({$text:{$search:"aa bb -cc"}})

#(与查询)加查询内容前后用""包含,查询既包含 aa 又包含 bb cc 的内容的文档
db.article.find({$text:{$search:'"aa" "bb" "cc"'}})

# 使用 $meta 操作符来查看相似度,并根据相似度来排序
db.imooc_2.find({$text:{$search:"aa bb"}},{score:{$meta:"textScore"}})
db.imooc_2.find({$text:{$search:"aa bb"}},{score:{$meta:"textScore"}}).sort({score:{$meta:"textScore"}});

在 MongoDB 中每个数据集合只能创建一个全文索引, 所以使用全文索引进行查询时不会起冲突.
在 3.2+ 的版本中支持了对中文的全文搜索。英文搜索中是按单词来匹配,也就是说内容有空格进行区分,如果是混在一起的字符串,那效果也非常的差。
注意事项:

  • 每次查询,只能指定一个 $text 查询
  • $text 查询不能出现在 $nor 查询中
  • 查询中如果包含了 $text, hint 不再起作用

全文索引会导致 mongodb 写入性能下降,因为所有字符串都要拆分,存储到不同地方。
还有就是虽然支持了中文,但是效果并不好,并且只有在企业版中才可以使用 rlp 之类,并且整句的话关键词搜索还是不好,解决方案现在有使用 Elastic Search 同步 mongodb,或者使用分词工具把分词存为单独的一个 tags。

其他

创建索引的时候系统会默认生成一个名字,为了可读性,我们可以手动定义生成索引的名字,就像这样:
db.articles.createIndex({x:1},{name:"test"})
就是说可以传入第二个参数,这里是 name 作为例子,其他的还有 unique(是否唯一)、sparse (稀疏性,默认 false)。
开启稀疏后当记录不存在索引字段时就不会创建索引,减少了磁盘的占用,但是带来的问题是强制使用索引查询(hint)时会出错(不存在)。


然后就是地理位置索引,目前感觉用不到,简单提提,根据平面(X/Y 坐标)或者球面来定位,比如查找距离某个点 一定距离的点,包含在某区域内的点。


评判索引构建情况(是否合理)的几种方式:

  • mongostat 工具
    自带的查看运行状态的工具,使用方法:mongostat -h 127.0.0.1:27017 有用户名密码的话再加 -u x -p x
    具体的状态说明,自行搜索(因为我没用到过,关注 qr/qw/idx miss)
  • profile 集合
    查看状态:db.getProfilingStatus() ,一个是级别,另一个 slowms 是慢查询的阀值。
    查看/设置等级:db.get/setProfilingLevel() 同时 set 也算是开启 profile ;
    等级有三种,加一个关闭状态:
    0:不开启;1:记录慢命令,默认为大于100ms;2:记录所有命令;3、查询 profiling 记录。
  • 日志
    配置文件里可以加入 berbose 来控制级别,数值在 1-5 个 V 之间。
  • explain 分析
    使用:db.demo.find({x:100}).explain()
    用来查看设置索引后是否起作用。

一些补充:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
db.system.profile.find()

{
"op" : "query",--操作类型
"ns" : "imooc.system.profile", --查询的命名空间,;databasename.collectionname'
"query" : { "query" : { }, --查询条件
"orderby" : { "$natural" : -1 } }, --约束条件
"ntoreturn" : 1, --返回数据条目
"ntoskip" : 0, --跳过的条目
"nscanned" : 1, --扫描的数目含索引
"nscannedObjects" : 1, --扫描的数据数目
"keyUpdates" : 0, --
"numYield" : 0, --其他情况
"lockStats" : { --锁状态
"timeLockedMicros" : { --锁占用时间(毫秒)
"r" : NumberLong(82), --读锁
"w" : NumberLong(0) --写锁
},
"timeAcquiringMicros" : {
"r" : NumberLong(2), "w" : NumberLong(2)
}
},
"nreturned" : 1,
"responseLength" : 651, --返回长度
"millis" : 0, --查询时间
}

线上环境不推荐开启 profile ,上线前的观察可以使用。

用户管理

设置用户名密码是最常用的保护措施,虽然安全性不是很高,但是方便;开启权限认证有两种方法:
auth(配置文件里加 auth = true 即可) 或者 keyfile。
然后还需要创建用户:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
> use admin
> db.createUser(
... {
... user: "dba",
... pwd: "dba",
... roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
... }
... )

# 角色说明
Built-In Roles(内置角色):
1. 数据库用户角色:read、readWrite;
2. 数据库管理角色:dbAdmin、dbOwner、userAdmin;
3. 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager;
4. 备份恢复角色:backup、restore;
5. 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase
6. 超级用户角色:root
// 这里还有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、userAdminAnyDatabase)
7. 内部角色:__system

Read:
允许用户读取指定数据库

readWrite:
允许用户读写指定数据库

dbAdmin:
允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile

userAdmin:
允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户

clusterAdmin:
只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。

readAnyDatabase:
只在admin数据库中可用,赋予用户所有数据库的读权限

readWriteAnyDatabase:
只在admin数据库中可用,赋予用户所有数据库的读写权限

userAdminAnyDatabase:
只在admin数据库中可用,赋予用户所有数据库的userAdmin权限

dbAdminAnyDatabase:
只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。

root:
只在admin数据库中可用。超级账号,超级权限

db.system.users.find();

帐号是跟着库走的,所以在指定库里授权,必须也在指定库里验证,账号信息存放在 admin 数据库中
开始时没进行登陆可以使用 db.auth('name','pwd') 来进行授权,返回 1 表示成功。
就说到这吧,虽然是最简单的一些操作。

拓展

更多内容参见:
https://www.cnblogs.com/TankMa/archive/2011/06/08/2074947.html
https://www.ibm.com/developerworks/cn/opensource/os-mongodb4/

评论框加载失败,无法访问 Disqus

你可能需要魔法上网~~