
04 MongoDB 笔记
MongoDB
MongoDB 相关概念
传统的关系型数据库(如MySQL),在数据操作的 “三高” 需求以及应对 Web2.0 的网站需求面前,显得力不从心。
- 解释 “三高” 需求:
- High performance:对数据库 高并发 读写的需求
- Huge Storage:对海量数据的 高效率存储和访问 的需求
- High Scalability && High Availability:对数据库的 高可扩展性和高可用性 的需求
而 MongoDB可应对“三高”需求
具体的应用场景如:
- 社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能
- 游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、高效率存储和访问
- 物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将 订单所有的变更读取出来
- 物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析
- 视频直播,使用 MongoDB 存储用户信息、点赞互动信息等
这些应用场景中,数据操作方面的共同特点是:
- 数据量大
- 写入操作频繁(读写都很频繁)
- 价值较低的数据,对 事务性 要求不高
对于这样的数据更适合使用 MongoDB 来实现数据的存储。
选用MongoDB的场景:
在架构选型上,除了上述的三个特点外,可以考虑以下的一些问题:
- 应用 不需要事务 及复杂 join 支持
- 新应用,需求会变,数据模型无法确定,想快速迭代开发
- 应用需要 2000-3000 以上的读写QPS(更高也可以)
- 应用需要 TB 甚至 PB 级别数据存储
- 应用发展迅速,需要能快速水平扩展
- 应用要求存储的数据不丢失
- 应用需要 99.999% 高可用
- 应用需要大量的地理位置查询、文本查询
MongoDB 简介
MongoDB 是一个 开源、高性能、无模式 的 文档型数据库,当初的设计就是用于简化开发和方便扩展,是 NoSQL数据库产品中的一种。是 最像关系型数据库(MySQL)的非关系型数据库
它支持的数据结构非常松散,是一种类似于 JSON 的 格式叫 BSON,所以它 既可以存储比较复杂的数据类型,又相当的灵活
MongoDB中的记录是一个文档,它是一个由 字段和值对(field:value)组成的数据结构。MongoDB文档类似于JSON对象,即一个文档认为就是一个对象。字段的数据类型是 字符型,它的值除了使用基本的一些类型外,还可以包括其他文档、普通数组和文档数组


MongoDB 数据模型
MongoDB的最小存储单位就是 文档(document)对象
- 文档(document)对象对应于关系型数据库的行
- 数据在MongoDB中以 BSON(Binary-JSON)文档 的格式存储在磁盘上
BSON(Binary Serialized Document Format)是一种 类 json 的一种二进制形式的存储格式,简称 Binary JSON
- BSON 和 JSON 一样,支持 内嵌的文档对象和数组对象,但是 BSON 有 JSON 没有的一些数据类型,如Date 和 BinData 类型
- BSON 采用了 类似于 C 语言结构体的名称、对表示方法,支持内嵌的文档对象和数组对象,具有轻量性、可遍历性、高效性的三个特点,可以有效描述非结构化数据和结构化数据。这种格式的优点是灵活性高,但它的缺点是空间利用率不是很理想
- BSON 中,除了基本的JSON类型:
string、integer、boolean、double、null、array和object,mongo 还使用了特殊的数据类型。这些类型包括:date、object id、binary data、regular expression和code。每一个驱动都 以特定语言的方式 实现了这些类型,查看你的驱动的文档来获取详细信息

MongoDB 特点
- 高性能:MongoDB提供 高性能的数据持久性
- 特别是, 对 嵌入式数据模型 的支持减少了数据库系统上的 I/O 活动
- 索引支持更快的查询,并且可以包含来自嵌入式文档和数组的键(文本索引解决搜索的需求、TTL索引解决历史数据自动过期的需求、地理位置索引可用于构建各种 O2O 应用)
- mmapv1、wiredtiger、mongorocks(rocksdb)、in-memory 等多引擎支持满足各种场景需求。
- Gridfs 解决文件存储的需求
- 高可用性:MongoDB的复制工具称为副本集(replica set),它可提供自动故障转移和数据冗余
- 高扩展性:MongoDB提供了水平可扩展性作为其核心功能的一部分。 分片将数据分布在一组集群的机器上。(海量数据存储,服务能力水平扩展) 从3.4开始,MongoDB支持基于片键创建数据区域。在一个平衡的集群中,MongoDB将一个区域所覆盖的读写只定向到该区域内的那些 片
- 丰富的查询支持:MongoDB支持丰富的查询语言,支持读和写操作(CRUD),比如:数据聚合、文本搜索和地理空间查询等
- 其他特点:如 无模式(动态模式)、灵活的文档模型
基本常用命令
举例:数据库 articledb

选择 和 创建 数据库
基本语法:
1 | use 数据库名称 |
- 如果 数据库不存在 则 自动创建,但是 use 创建的数据库并没有真正创建,而是在内存中,并没有持久化到磁盘,只有在创建数据表后要再插入一个文档(记录),数据库才会真正创建
eg:
1 | use articledb |
查看 所有的数据库 命令:
1 | show dbs |
- admin: 从权限的角度来看,这是 “root” 数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如:列出所有的数据库或者关闭服务器
- local:这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合,在集群中使用较多
- config:当 Mongo 用于分片设置时,config 数据库在内部使用,用于保存分片的相关信息
查看 当前正在使用的数据库 命令:
1 | db |
eg:
1 | > db |
另外: 数据库名可以是满足以下条件的任意 UTF-8 字符串
- 不能是空字符串(
"") - 不得含有
' '(空格)、.、$、/、\和\0(空字符) - 应全部小写
- 最多 64 字节
删除 数据库
基本语法:
1 | db.dropDatabase() |
- 提示:主要用来 删除已经持久化的数据库
eg:
1 | > db.dropDatabase() |
集合 操作
集合,类似关系型数据库中的 表
集合 的 显式创建
基本语法:
1 | db.createCollection(name) |
参数说明:
name:要创建的集合名称
eg:
1 | > db.createCollection("mycollection") |
集合 的 删除
基本语法:
1 | db.collection.drop() |
- 返回值说明:如果成功删除选定集合,则 drop() 方法返回 true,否则返回 false
eg:
1 | > db.mycollection.drop() |
文档基本 CRUD
文档(document)的数据结构和 JSON 基本一样
所有存储在集合中的数据都是 BSON 格式
文档 的 插入
单个文档的插入:
使用 insert() 或 save() 方法向集合中插入文档
基本语法:
1 | db.collection.insert( |
- 参数说明:

eg:
1 | > db.comment.insert({"articleid":100000, "content":"今天天气真不错", "userid":"1001","nickname":"阿三","createtime":new Date(), "likenum":NumberInt(10),"state":null}) |
查询是否插入成功:
1 | > db.comment.find() |
说明:
- comment 集合如果不存在,则会隐式创建
- mongo 中的数字,默认情况下是 double 类型,如果要存整型,必须使用函数 NumberInt (整型数字),否则取出来就有问题了
- 插入当前日期使用 new Date()
- 插入的数据没有指定 _id ,会自动生成主键值
- 如果某字段没值,可以赋值为 null,或不写该字段
批量插入:
基本语法:
1 | db.collection.insertMany( |
eg:
1 | > db.comment.insertMany([ |
- 插入时指定了 _id ,则主键就是该值
- 如果某条数据插入失败,将会终止插入,但已经插入成功的数据不会回滚掉
- 因为批量插入由于数据较多容易出现失败,因此,可以使用 try catch 进行异常捕捉处理,测试的时候可以不处理
文档 的 基本查询
基本语法:
1 | db.collection.find(<query>, [projection]) |
- 参数说明:

eg:
-
查询所有:
1
2
3db.comment.find()
或者
db.comment.find({}) -
按一定条件来查询
1
db.comment.find({userid:"1003"})
-
查询并只需要返回符合条件的第一条数据
1
db.comment.findOne({userid:"1003"})
-
投影查询
1
2
3
4
5# 查询结果只显示 _id、userid、nickname
# 默认 _id 会显示
> db.comment.find({userid:"1003"},{userid:1,nickname:1})
{ "_id" : "4", "userid" : "1003", "nickname" : "凯撒" }
{ "_id" : "5", "userid" : "1003", "nickname" : "凯撒" }- 如果 要查询结果返回部分字段,则需要使用投影查询(不显示所有字段,只显示指定的字段)
1
2
3
4# 查询结果只显示 userid、nickname,不显示 _id
> db.comment.find({userid:"1003"},{userid:1,nickname:1,_id:0})
{ "userid" : "1003", "nickname" : "凯撒" }
{ "userid" : "1003", "nickname" : "凯撒" }
注意:
批量插入由于数据较多容易出现失败,因此,可以使用 try catch 进行异常捕捉处理,测试的时候可以不处理
eg:
1 | try { |
文档 的更新
基本语法:
1 | db.collection.update(query, update, options) |
eg:
覆盖修改:
1 | db.comment.update({_id:"1"},{likenum:NumberInt(1001)}) |
- 执行后会发现,这条文档 除了 likenum 字段 其它字段都不见了
局部修改:
1 | db.comment.update({_id:"1"},{$set:{likenum:NumberInt(1001)}) |
- 解决覆盖修改的问题,需要使用修改器
$set来实现
批量修改:
1 | # 修改所有符合条件的数据 |
- 提示:如果不加后面的参数,则只更新符合条件的第一条记录
- MongoDB 修改默认只修改 第一条数据
列值增长的修改
1 | # 对3号数据的点赞数,每次递增1 |
- 实现对某列值在原有值的基础上进行增加或减少,可以使用
$inc运算符来实现
文档 的 删除
基本语法:
1 | db.集合名称.remove(条件) |
eg:
数据全部删除:
1 | db.comment.remove({}) |
删除 _id=1的记录:
1 | db.comment.remove({_id:"1"}) |
文档 的 分页查询
统计查询
统计查询使用 count() 方法,基本语法:
1 | db.collection.count(query, options) |
参数说明:
eg:
统计所有记录数:
1 | db.comment.count() |
按条件统计记录数:
1 | > db.comment.count({articleid:"100001"}) |
分页列表查询
使用 limit() 方法来 读取指定数量的数据,使用 skip() 方法来 跳过指定数量的数据
基本语法:
1 | db.collection.find().limit(NUMBER).skip(NUMBER) |
eg:
要返回指定条数的记录,可以在 find 方法后调用 limit 来返回结果(TopN),默认值 20
1 | db.comment.find().limit(3) |
skip 方法接受一个数字参数作为跳过的记录条数。(前N个不要),默认值是 0
1 | > db.comment.find().skip(3) |
排序查询
sort() 方法对数据进行排序,sort() 方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中:1 为 升序 排列,而 -1 是于 降序 排列
基本语法:
1 | db.collection.find().sort({KEY:1}) |
eg:
对 userid 降序排列,并对 访问量 进行升序排列:
1 | db.comment.find().sort({userid:-1, likenum:1}) |
注意:
skip()、limilt()、sort() 三个放在一起执行的时候,执行的顺序是 先 sort(),然后是 skip(),最后是显示的 limit(),和命令编写顺序无关
文档 的 复杂查询
正则的复杂条件查询
MongoDB 的模糊查询是通过 正则表达式 的方式实现的
基本语法:
1 | db.collection.find({field:/正则表达式/}) |
eg:
查询评论内容包含 “开水” 的所有文档:
1 | > db.comment.find({content:/开水/}) |
比较查询
<、<=、>、>= 这种 操作符 是很常用的,基本语法:
1 | # 大于: field > value |
eg:
查询评论点赞数大于2500的记录:
1 | > db.comment.find({likenum:{$gt:NumberInt(2500)}}) |
包含查询
包含 使用 $in 操作符
eg:查询评论的集合中 userid 字段包含1003 或 1004 的文档
1 | db.comment.find({userid:{$in:["1003", "1004"]}}) |
不包含 使用 $nin 操作符
eg:查询评论集合中 userid 字段不包含 1003 和 1004 的文档
1 | db.comment.find({userid:{$nin:["1003", "1004"]}}) |
条件连接查询
如果需要查询 同时满足两个以上条件,需要使用 $and 操作符将条件进行关联。(相当于 SQL 的 and)
基本语法:
1 | $and:[ { },{ },{ } ] |
eg:
查询评论集合中 likenum 大于等于 700 并且小于 2000 的文档
1 | db.comment.find({$and:[{likenum:{$gte: NumberInt(700)}},{likenum:{$lt: NumberInt(2000)}}]}) |
如果 两个以上条件之间是 或者 的关系,需要使用 $or 操作符将条件进行关联
1 | $or:[ { },{ },{ }] |
eg:
查询评论集合中 userid 为 1003,或者点赞数小于 1000 的文档记录
1 | db.comment.find({$or:[{userid: "1003"}, {likenum:{$lt: NumberInt(1000)}]}) |
总结:
1 | # 选择切换数据库 |
索引
MongoDB 索引使用 B树 数据结构(确切的说是 B-Tree,MySQL是 B+Tree)
索引的分类
- 单字段索引
- MongoDB 支持在文档的单个字段上创建用户定义的 升序/降序 索引
- 复合索引
- 支持多个字段的用户定义索引
- 地理空间索引(Geospatial Index)
- 文本索引(Text Indexes)
- 哈希索引(Hashed Indexes)
索引的管理操作
查看索引
返回 一个集合中 的 所有索引 的 数组
基本语法:
1 | db.collection.getIndexes() |
eg:
1 | > db.comment.getIndexes() |
- 结果中显示的是默认
_id索引
注意:该索引是唯一索引,因此值不能重复,即 _id 值不能重复的。在分片集群中,通常使用 _id 作为片键
创建索引
基本语法:
1 | db.collection.createIndex(keys, options) |
-
参数说明:


eg:
对 userid 字段建立一个升序的索引
1 | > db.comment.createIndex({userid:1}) |
对 userid 和 nickname 同时建立 复合索引
1 | > db.comment.createIndex({userid:1, nickname:-1}) |
删除索引
基本语法:
1 | db.collection.dropIndex(index) |
- 参数说明:

eg:
删除 comment 集合中 userid 字段上的升序索引:
1 | > db.comment.dropIndex({userid:1}) |
所有索引的移除:
1 | > db.comment.dropIndexes() |
- 提示:
_id的字段的索引是无法删除的,只能删除非_id字段的索引
索引的使用
执行计划
分析查询性能(Analyze Query Performance)通常使用执行计划(解释计划、Explain Plan)来查看查询的情况,如:查询耗费的时间、是否基于索引查询等
基本语法:
1 | db.collection.find(query, options).explain(options) |
eg:
1 | > db.comment.find({userid:"1003"}).explain() |
涵盖的查询
当查询条件和查询的投影仅包含索引字段时,MongoDB 直接从索引返回结果,而不扫描任何文档或将文档带入内存,这些覆盖的查询可以非常有效
详情见连接:查询优化 - MongoDB 手册 v7.0
SpringDataMongoDB
SpringData 家族成员之一,用于操作 MongoDB 的持久层框架,封装了底层的 mongodb-driver
官网主页: https://projects.spring.io/spring-data-mongodb/
使用流程
-
导入相关依赖
1
2
3
4
5
6
7
8
9
10
11<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
</dependencies> -
创建 application.yml
1
2
3
4
5
6
7
8
9
10
11
12spring:
#数据源配置
data:
mongodb:
# 主机地址
host: 192.168.88.129
# 数据库
database: test
# 默认端口是27017
port: 27017
#也可以使用uri连接
#uri: mongodb://192.168.88.129:27017/test -
创建启动类
1
2
3
4
5
6
public class ArticleApplication {
public static void main(String[] args) {
SpringApplication.run(ArticleApplication.class, args);
}
} -
创建一个 article 的实体类
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135/**
* 文章评论实体类
*/
// 把一个java类声明为mongodb的文档,可以通过collection参数指定这个类对应的文档。
// @Document(collection="mongodb 对应 collection 名")
// 若未加 @Document ,该 bean save 到 mongo 的 comment collection
// 若添加 @Document ,则 save 到 comment collection
// 可以省略,如果省略,则默认使用类名小写映射集合
// 复合索引
public class Comment implements Serializable {
// 主键标识,该属性的值会自动对应mongodb的主键字段"_id",如果该属性名就叫“id”,则该注解可以省略,否则必须写
private String id; //主键
// 该属性对应mongodb的字段的名字,如果一致,则无需该注解
private String content; //吐槽内容
private Date publishtime; //发布日期
// 添加了一个单字段的索引
private String userid; // 发布人ID
private String nickname; // 昵称
private LocalDateTime createdatetime; // 评论的日期时间
private Integer likenum; // 点赞数
private Integer replynum; // 回复数
private String state; // 状态
private String parentid; // 上级ID
private String articleid;
// getter and setter.....
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getPublishtime() {
return publishtime;
}
public void setPublishtime(Date publishtime) {
this.publishtime = publishtime;
}
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public LocalDateTime getCreatedatetime() {
return createdatetime;
}
public void setCreatedatetime(LocalDateTime createdatetime) {
this.createdatetime = createdatetime;
}
public Integer getLikenum() {
return likenum;
}
public void setLikenum(Integer likenum) {
this.likenum = likenum;
}
public Integer getReplynum() {
return replynum;
}
public void setReplynum(Integer replynum) {
this.replynum = replynum;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getParentid() {
return parentid;
}
public void setParentid(String parentid) {
this.parentid = parentid;
}
public String getArticleid() {
return articleid;
}
public void setArticleid(String articleid) {
this.articleid = articleid;
}
public String toString() {
return "Comment{" +
"id='" + id + '\'' +
", content='" + content + '\'' +
", publishtime=" + publishtime +
", userid='" + userid + '\'' +
", nickname='" + nickname + '\'' +
", createdatetime=" + createdatetime +
", likenum=" + likenum +
", replynum=" + replynum +
", state='" + state + '\'' +
", parentid='" + parentid + '\'' +
", articleid='" + articleid + '\'' +
'}';
}
}说明:
索引可以大大提升查询效率,一般在查询字段上添加索引,索引的添加可以通过 Mongo 的命令来添加,也可以在 Java 的实体类中通过注解添加
- 单字段索引注解
@Indexed - 复合索引注解
@CompoundIndex
- 单字段索引注解
未完待续 …






