MongoDB

·本篇:4.4k字 大约需要: 18分钟

MongoDB

特点

  • 面向集合存储,易存储对象类型的数据
  • 支持查询及动态查询
  • 支持RUBY、Python、JAVA、C++、PHP、C#等多种语言
  • 文件存储格式为BSON(一种JSON的扩展)
  • 支持复制和故障恢复及分片

应用场景

  • 游戏应用:使用云数据库MongoDB作为游戏服务器的数据库存储用户信息,用户的游戏装备、积分等直接以内嵌文档的形式存储,方便进行查询与更新

  • 物流应用:使用云数据库MongoDB存储订单信息,订单状态在运送过程中会不断更新,以云数据库MongoDB内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来,方便快捷且一目了然

  • 社交应用:使用云数据库MongoDB存储用户信息以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能,并且,云数据库MongoDB非常适合用来存储聊天记录,因为它提供了非常丰富的查询,并在写入和读取方面都相对较快

  • 视频直播:使用云数据库MongoDB存储用户信息、礼物信息等

  • 大数据应用:使用云数据库MongoDB作为大数据的云存储系统,随时进行数据提取分析,掌握行业动态

核心概念

MongoDB中的库就类似于传统关系型数据库中库的概念,用来通过不同库隔离不同应用数据,MongoDB中可以建立多个数据库,每一个库都有自己的集合和权限,不同的数据库也放置在不同的文件中。默认的数据库为test,数据库存储在启动指定的data目录中

集合

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

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

文档

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

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

1
{"site": "test", "name": "t1k"}

关系总结

RDBMS MongoDB
数据库 数据库
集合
文档
字段

基本操作

查看所有库

1
2
show databases;
show dbs;
1
2
3
4
> show dbs;
admin 0.000GB
config 0.000GB
local 0.000GB

注意:

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

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

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

创建数据库

1
use 库名
1
2
3
4
5
> use product
switched to db product
> db
product
>

use 代表创建并使用,当库中没有数据时默认不显示这个库

删除数据库

默认删除当前选中的库

1
db.dropDatabase()

集合

查看库中所有集合

1
2
show collections;
show tables;

创建集合

1
db.createCollection('集合名称', [options])

options可以是如下参数:

字段 类型 描述
capped 布尔 (可选)如果为true,则创建固定集合,固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。当改值为true时,必须指定size参数
size 数值 (可选)为固定集合指定一个最大值,即字节数,如果capped为true,也需要指定该字段
max 数值 (可选)指定固定集合中包含文档的最大数量

注意:当集合不存在时,向集合中插入文档也会自动创建该集合

删除集合

1
db.集合名称.drop()

文档

插入文档

单条文档
1
db.集合名称.insert({"name": "小明", "age": 23, "bir": "2001-01-01"})
多条文档
1
2
3
4
5
6
7
8
9
10
11
12
db.集合名称.insertMany(
[<document 1>, <document 2>, ···],
{
writeConcern: 1, // 写入策略,默认为1,即要求确认写操作,0是不要求
ordered: true // 指定是否要按顺序写入,默认为true,按顺序写入
}
)

db.集合名称.insert([
{"name": "小明", "age": 23, "bir": "2001-01-01"},
{"name": "小红", "age": 23, "bir": "2001-01-01"}
])
脚本方式
1
2
3
for(let i = 0; i < 100; i++){
db.集合名称.insert({"_id": i, "name": "小明_" + i, "age": 23})
}

注意:在MongoDB中每个文档都会有一个_id作为唯一标识,_id默认会自动生成,如果手动指定将使用手动指定的值作为_id的值

查询所有

1
db.集合名称.find()

删除文档

1
2
3
4
5
6
7
8
9
10
db.集合名称.remove(
<query>,
{
justOne: <boolean>,
writeConcern: <document>
}
)
// 删除所有
db.集合名称.remove({})
db.集合名称.remove(age:21)

参数说明:

  • query:(可选)删除文档的条件
  • justOne:(可选)如果设为true或1,则只删除一个文档,如果不设置该参数,或使用默认值false,则删除所有匹配条件的文档
  • writeConcern:(可选)抛出异常的级别

更新文档

1
2
3
4
5
6
7
8
9
db.集合名称.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:可选,抛出异常的级别

1
2
3
4
5
6
7
8
// 这个更新是将符合条件的第一条文档(除id外)全部更新为后面的文档,相当于先删除再更新
db.集合名称.update({"name": "张三", {"name": "李四", "bir": new date()}})
// 保留原来数据更新,但是只更新符合条件的第一条数据,如果原文档没有更新的字段,则在原文档基础上增加该字段
db.集合名称.update({"name": "小红", {$set: {"name": "明明"}})
// 保留原来数据更新,更新符合条件的所有数据
db.集合名称.update({"name": "小红", {$set: {"name": "小明"}}, {multi:true})
// 保留原来数据更新,更新符合条件的所有数据,没有条件符合时插入数据
db.集合名称.update({"name": "小红", {$set: {"name": "小明"}}, {multi:true, upsert: true})

文档查询

MongoDB查询文档使用find()方法,find()方法以非结构化的方式来显示所有文档

1
db.集合名称.find(query, projection)
  • query:可选,使用查询操作符指定查询条件

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

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

1
db.集合名称.find().pretty()
对比语法

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

操作 格式 范例 RDBMS中的类似语句
等于 {;} db.集合名称.find({“name”: “小明”}).pretty() where name = ‘小明’
小于 {;{$lt: }} db.集合名称.find({“age”: {$lt: 30}}).pretty() where age < 30
小于或等于 {;{$lte: }} db.集合名称.find({“age”: {$lte: 30}}).pretty() where age <= 30
大于 {;{$gt: }} db.集合名称.find({“age”: {$gt: 30}}).pretty() where age > 30
大于或等于 {;{$gte: }} db.集合名称.find({“age”: {$gte: 30}}).pretty() where age >= 30
不等于 {;{$ne: }} db.集合名称.find({“age”: {$ne: 30}}).pretty() where age != 30
AND
1
2
db.集合名称.find({key1: value1, key2: value2}).pretty()
AND查询中,如果key1和key2是同一个字段,那么后一个查询条件会覆盖掉前一个查询条件

类似于 WHERE 语句:WHERE key1=value1 AND key2=value2

OR

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

1
2
3
4
5
6
7
db.集合名称.find(
{
$or: [
{key1: value1}, {key2: value2}
]
}
).pretty()

类似于 WHERE 语句:WHERE key1=value1 or key2=value2

AND和OR联合

类似SQL语句为:where age > 50 and (name = '小明' or name = '小红')

1
db.集合名称.find({age: {$gt: 50}, $or: [{name: "小红"}, {name: "小红"}]}).pretty()
数组中查询
1
2
3
4
db.集合名称.insert({_id: 1, age: 23, likes: ["看书", "学习"], name: "小明"})
db.集合名称.find({likes: "看书"})
-- $size 按照数组长度查询
db.集合名称.find({likes: {$size:2}})
模糊查询

类似SQL中:where name like '%name%'

1
2
-- 使用正则表达式实现
db.集合名称.find({name: //})

注意:在MongoDB中使用正则表达式可以实现近似模糊查询功能

排序
1
2
3
db.集合名称.find().sort({name: 1, age: 1})
-- 1 升序
-- -1降序

类似SQL语句为:order by name, age

分页

类似SQL语句:limit start, rows

1
db.集合名称.find().sort({条件}).skip(start).limit(rows)
总条数

类似于SQL语句:select count(id) from ···

1
2
db.集合名称.count()
db.集合名称.find({name: "小明"}).count()
去重
1
db.集合名称.distinct('字段')
指定返回字段
1
2
db.集合名称.find({条件}, {name: 1, age: 1})
-- 参数2:1 返回,0 不返回 注意:1和0不能同时使用

$type

说明

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

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

类型 数字 备注
Double 1
String 2
Object 3
Array 4
Binary data 5
Undefined 6 已废弃
Object id 7
Boolean 8
Date 9
1
2
db.集合名称.find({name: {$type: 2}}).pretty()
db.集合名称.find({name: {$type: "string"}}).pretty()

索引

https://docs.mongodb.com/manual/indexes/

说明

索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录,这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的,索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构

原理

索引原理图

从根本上说,MongoDB中的索引与其它数据库系统中的索引类似,MongoDB在集合层面上定义了索引,并支持对MongoDB集合中的任何字段或文档的子字段进行索引

操作

创建索引
1
2
db.集合名称.createIndex(keys, options)
db.集合名称.createIndex({"title": 1, "description": -1})

说明:语法中 Key 值为你要创建的索引字段,1 为指定按升序创建索引,-1为按降序创建索引

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

Parameter Type Description
background Boolean 创建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加”background”可选参数,”background”默认值为false
unique Boolean 建立的索引是否唯一,指定为true创建唯一索引,默认值为false
name string 索引的名称,如果未指定,MongoDB会通过连接缩印的字段名和排序顺序生成一个索引名称
expireAfterSeconds integer 指定一个以秒为单位的数值,完成TTL设定,设定索引的生成时间
查看集合索引
1
db.集合名称.getIndexes()
查看集合索引大小
1
db.集合名称.totalIndexSize()
删除集合所有索引
1
db.集合名称.dropIndexes()
删除集合指定索引
1
db.集合名称.dropIndex("索引名称")

复合索引

说明

一个索引的值是由多个key进行维护的索引称之为复合索引

1
db.集合名称.createIndex({"title": 1, "description": -1})

注意:MongoDB中复合索引和传统关系型数据库一致都是左前缀原则

多字段建立索引

排序时使用索引

聚合

说明

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

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
title: 'MongoDB Overview',
description: 'MongoDB is no sql database',
by_user: 'runoob.com',
url: 'http://www.runoob.com',
tags: ['mongodb', 'database', 'NoSQL'],
likes: 100
},
{
title: 'NoSQL Overview',
description: 'NoSQL database is very fast',
by_user: 'runoob.com',
url: 'http://www.runoob.com',
tags: ['mongodb', 'database', 'NoSQL'],
likes: 10
},
{
title: 'Neo4j Overview',
description: 'Neo4j is no sql database',
by_user: 'Neo4j',
url: 'http://www.runoob.com',
tags: ['mongodb', 'database', 'NoSQL'],
likes: 750
}

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

1
db.集合名称.aggregate([{$group :{_id: "$by_user", "sum_by_user": {$sum: 1}}}])

常见聚合表达式

表达式 描述 实例
$sum 计算总和 db.集合名称.aggregate([{$group :{_id: “$by_user”, “sum_by_user”: {$sum: 1}}}])
$avg 计算平均值 db.集合名称.aggregate([{$group :{_id: “$by_user”, “avg_by_likes”: {$avg: “$likes”}}}])
$min 获取集合中所有文档对应值的最小值 db.集合名称.aggregate([{$group :{_id: “$by_user”, “min_by_likes”: {$avg: “$likes”}}}])
$max 获取集合中所有文档对应值的最大值 db.集合名称.aggregate([{$group :{_id: “$by_user”, “max_by_likes”: {$avg: “$likes”}}}])
$push 将值加入一个数组中,不会判断是否有重复的值 db.集合名称.aggregate([{$group :{_id: “$by_user”, “push_by_url”: {$push: “$url”}}}])
$addToSet 将值加入一个数组中,会判断是否有重复的值 db.集合名称.aggregate([{$group :{_id: “$by_user”, “addToSet_by_url”: {$addToSet: “$url”}}}])
$first 根据资源文档的排序获取第一个文档数据 db.集合名称.aggregate([{$group :{_id: “$by_user”, “first_url”: {$first: “$url”}}}])
$last 根据资源文档的排序获取最后一个文档数据 db.集合名称.aggregate([{$group :{_id: “$by_user”, “last_url”: {$last: “$url”}}}])

副本集

说明

https://docs.mongodb.com/manual/replication/

MongoDB副本集(Replica Set)是有自动故障恢复功能的主从集群,有一个Primary节点和一个或多个Secondary节点组成,副本集没有固定的主节点,当主节点发生故障时整个集群会选举一个主节点为系统提供服务以保证系统的高可用

副本集原理图

Automatic Failover

自动故障转移机制:当主节点未与集合的其他成员通信超过配置的选举超时时间(默认为10秒)时,合格的辅助节点将调用选举以将自己提名为新的主节点,集群尝试完成新主节点的选举并恢复正常操作

从节点选举为主节点

SpringBoot整合MongoDB

Spring Data Mongodb

和Spring Data Elasticsearch类似,Spring Data Mongodb是Spring提供的一种以Spring Data风格来操作数据存储的方式,它可以避免编写大量的样板代码

常用注解

  • @Document:表示映射到Mongodb文档上的领域对象
  • @Id:表示某个域的id
  • @Indexed:表示某个字段为Mongodb的索引字段

Spring Data方式的数据操作

添加pom.xml依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

修改SpringBoot配置文件

1
2
3
4
5
6
spring:
data:
mongodb:
host: 127.0.0.1
port: 27017
database: mall-test

添加会员浏览记录文档对象MemberReadHistory

文档对象的ID域添加@Id注解,需要检索的字段添加@Indexed注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 用户商品浏览历史记录
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document
public class MemberReadHistory
{
@Id
private String id;
@Indexed
private Long memberId;
private String memberNickname;
private String memberIcon;
@Indexed
private Long productId;
private String productName;
private String productPic;
private String productSubTitle;
private String productPrice;
private Date createTime;
}

添加MemberReadHistoryRepository接口用于操作Mongodb

继承MongoRepository接口,同时定义一个衍生查询方法

1
2
3
4
5
6
7
8
public interface MemberReadHistoryRepository extends MongoRepository<MemberReadHistory, String>
{
/**
* 根据会员id按时间倒序获取浏览记录
* @param memberId 会员Id
*/
List<MemberReadHistory> findByMemberIdOrderByCreateTimeDesc(Long memberId);
}

添加MemberReadHistoryService接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 会员浏览记录管理Service
*/
public interface MemberReadHistoryService {
/**
* 生成浏览记录
*/
void create(MemberReadHistory memberReadHistory);

/**
* 批量删除浏览记录
*/
int delete(List<String> ids);

/**
* 获取用户浏览历史记录
*/
List<MemberReadHistory> list(Long memberId);
}
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
@Service
public class MemberReadHistoryServiceImpl implements MemberReadHistoryService
{
@Resource
private MemberReadHistoryRepository memberReadHistoryRepository;

@Override
public void create(MemberReadHistory memberReadHistory)
{
memberReadHistory.setId(null);
memberReadHistory.setCreateTime(new Date());
memberReadHistoryRepository.save(memberReadHistory);
}

@Override
public int delete(List<String> ids)
{
List<MemberReadHistory> deleteList = ids.stream().map(id -> {
MemberReadHistory memberReadHistory = new MemberReadHistory();
memberReadHistory.setId(id);
return memberReadHistory;
}).collect(Collectors.toList());
memberReadHistoryRepository.deleteAll(deleteList);
return ids.size();
}

@Override
public List<MemberReadHistory> list(Long memberId)
{
return memberReadHistoryRepository.findByMemberIdOrderByCreateTimeDesc(memberId);
}
}