MongoDB是一个基于分布式文件存储的数据库,由C++语言编写,旨在为WEB应用提供可扩展的高性能数据存储解决方案。

MongoDB是介于关系数据库非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。

Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引

应用场景

MongoDB相较于传统的关系型数据库,可应对”三高”需求

  • High performance:对数据库高并发读写的需求
  • Huge Storage:对海量数据的高效率存储和访问的需求
  • High Scalability && High Availability:对数据库的高可扩展性和高可用性的需求

什么时候选择MongoDB

  • 新应用,需求会变,数据模型无法确定,想快速迭代开发
  • 应用需要2000-3000以上的读写QPS(更高也可以)
  • 应用需要TB甚至PB级别数据存储
  • 应用要求存储的数据不丢失
  • 应用需要99.999%高可用

体系结构

SQL术语/概念 MongoDB术语/概念 说明
database database 数据库
table collection 数据库表/集合
row document 数据记录行/文档
column field 数据字段/域
index index 索引
primary key primary key 主键

数据类型

MongoDB的最小存储单位是文档(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优点

  1. 高性能

​ MongoDB提供高性能的数据持久性。特别是对嵌入式数据模型的支持减少了数据库系统上I/O活动。索引支持更快的查询,并且可以包含来自嵌入式文档和数组的键。

  1. 高可用性
    MongoDB的复制工具称为副本集(replica set),它可提供自动故障转移和数据冗余

  2. 高扩展性
    MongoDB提供了水平可扩展性作为其核心功能的一部分。分片将数据分布在一组集群的机器上。(海量数据存储,服务能力水平扩展)

    从3.4开始,MoηgoDB支持基于片键创建数据区域。在一个平衡的集群中, MongoDB将一个区域所覆盖的读写只定向到该区域内的那些片。

  3. 丰富的查询支持
    MongoDB支持丰富的査询语言,支持读和写操作(CRUD),比如数据聚合、文本搜索和地理空间查询等

数据迁移

有时因为数据库数据量过大,查询耗时很长,可以考虑将MySQL中的数据迁移到Mongo中,如果对业务可接受停机,最简单的是冷更新(停服务 → 全量导出导入 → 切换服务),如果业务要求高可用,常用热更新(全量迁移 + 增量同步 → 平滑切换)

如果在迁移过程中有新的写请求过来怎么办

新的写操作照常落到MySQL,mongo这边通过Canal来订阅MySQL的binlog捕捉所有增量,实时同步到Mongo,这样迁移过程中写入的数据也能同步过去

如果迁移过程中MySQL有更新,导致两边数据不一致怎么办

全量迁移开始 → 同时开启binlog订阅

  • 在全量迁移的同时,就启动增量订阅(Canal / Debezium / Flink CDC)
  • 把MySQL的变更事件先缓存起来(比如Kafka队列),防止遗漏

全量迁移完成 → 回放增量日志

  • 全量迁移跑完之后,把在迁移过程中捕获到的binlog事件依次回放到Mongo
  • 这样可以保证在全量迁移过程中发生的更新也能被补齐

常用命令

需要用到的时候可以查阅MongoDB官方文档,在此列举常用命令

基本命令

mongosh:打开一个连接到本地实例的MongoShell。所有其他命令都需要在mongosh中执行

show databases\show dbs:显示当前MongoDB实例中的所有数据库

use <dbname>:切换到dbname数据库

cls: 清屏

show collections: 显示数据库中的所有集合

db.dropDatabase():删除当前数据库

exit: 退出

创建、插入

insertOne:在集合中插入一个新的文档,如果集合存在,那么直接插入数据。如果集合不存在,那么会隐式创建。

  • db.test.insertOne({name: "Jack", age: 18}):向test数据库中插入一条数据

insertMany:批量插入文档

快速插入

由于mongodb底层使用JS引擎实现的,所以支持部分Js语法。因此可以写for循环

for (var i=1; i<=10; i++) { db.c2.insertOne({uanme: "a"+i, age: i}) }

删除

delectOne:删除满足条件的第一个文档

delectMany:删除满足条件的所有文档

更新

updateOne:更新满足条件的第一个文档

updateMany:更新满足条件的所有文档

replaceone:替换满足条件的第一个文档

save:通过传入的文档替换已有文档或插入一个新的文档

$set:只更新文档中$set指定的字段

  • db.test.update({name:"Jack"},{$set:{uname:"Candy"}}):把jack改成Candy

$inc:用于递增/递减文档中指定字段值的操作符

$rename:更新某个字段的名称

$unset:删除一个字段

$push:将值加入一个数组中,不会判断是否有重复的值

$pull:将值从一个数组中移除

$addToSet:将值加入一个数组中,如果数组中有重复的值则不会加入

查找

find:查询所有文档

find(<filterObject>):查询所有满足参数对象<filterObject>中指定过滤条件的数据

  • db.test.find({age: 18}):查询所有年龄为18岁的文档

db.find(<filterObject>, <selectObject>):查询所有满足参数对象<filterObject>中指定过滤条件的数据,并且只返回或者不返回<selectObject>中指定的字段

  • db.test.find({}, {name:1}):只看name列
  • db.test.find({}, {name:0}):除了name列都看

findOne:与find用法相同,但是只返回1条

countDocuments:返回满足条件的记录的数量

sort:使用给定的字段按照升序或降序排序

  • db.test.find().sort({age:1}):按照年龄升序排序(传入-1是降序排序)

limit:限定只返回给定数量的文档

  • db.test.find().limit(1):只返回一条数据

skip:从头开始跳过给定数值的文档

  • db.test.find().limit(x).skip(y*x):实现翻页效果,一页x个内容,翻y页

聚合函数

$sum:计算总和

$avg:计算平均值

$min:计算最小值

$max:计算最大值

$first:获取第一个文档数据

$last:获取最后一个文档数据

过滤条件

$eq:等于

$ne:不等于

$gt\$get:大于\大于等于

  • db.c1.find({age:{$gt:5}}):查询age大于5的数据

$lt\$lte:小于\小于等于

$in:值在指定列表中就返回文档

  • db.c2.find({age:{$in:[5,8,10]}}):查询年龄是5,8,10的数据

$nin:值不在指定列表中就返回文档

$and:检查复数条件是否均为真,可以理解为“并且”

  • db.test.find({$and:[{age:{$gte:10}}, {age:{$lte:20}}}]}):返回年龄在10-20岁之间的文档

$or:检查复试条件是否有一个为真,可以理解为“或者”

$not:逻辑区取反

$exists:检查一个字段是否存在

$expr:在不同的字段中作比较

Java中操作MongoDB

首先需要导入mongodb驱动包

<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.12.14</version>
</dependency>

创建链接对象

MongoClient mongoClient = new MongoClient("127.0.0.1", 27017);

查看数据库

//获取库对象,参数为库名
MongoDatabase db = mongoClient.getDatabase("test");
//获取当前库对象的所有集合名的迭代器
MongoIterable<String> list = db.getlistCollectionNames();
for(String str:list){
System.out.println(str);//获取所有表
}
//获取集合对象,参数为集合名
MongoCollention<Document> collection = db.getCollection("student");

对数据库进行增删改查

新增数据

//获取集合对象
MongoCollection<Document> collection = db.getCollection("test");
//新增时创建一个Docuement对象,以键值对的形式传入内容
Document document = new Document();
document.put("name", "张三");
document.put("age", 21);
document.put("sex", "男");
//添加一条数据,没有返回值
collection.insertOne(document);
//新增多条数据,传入一个document集合
collection.insertMany(null);

Filter类

删除,修改,查询时传入的筛选条件,比如Bson eq = Filters.eq("name","张三");

常用方法
方法 说明
Filters.eq() 等值
Filters.gt() 大于指定值(gte大于等于)
Filters.lt() 小于指定值(lte小于等于)
Filters.ne() 不等于指定
Filters.nin() 不等于数组中的值
Filters.and() 传入多个Bson对象,and连接
Filters.regex() 模糊查询
Filters.exists() 存在改字段

删除数据

//条件
Bson eq = Filters.eq("name","张三");
//删除一条符合的
DeleteResult deleteOne = collection.deleteOne(eq);
//删除 所有符合条件的
DeleteResult deleteMany = collection.deleteMany(eq);

修改的返回值内容

AcknowledgedDeleteResult{deletedCount=0}

deletedCount:被删除的文档数

修改数据

//修改条件
Bson eq = Filters.eq("name","张三");
//修改匹配到的第一条
UpdateResult updateOne = collection.updateOne(
eq, new Document("$set",new Document("age",50)));
//修改匹配的多条
collection.updateMany(eq, null);

修改的返回值内容

AcknowledgedUpdateResult{matchedCount=0, modifiedCount=0, upsertedId=null}

matchedCount:代表匹配到的文档数

modifiedCount:代表被修改的文档数

upsertedId:代表修改的文档id(主键)

查询数据

//无条件全查
FindIterable<Document> find = collection.find();
//带条件查询
Bson eq = Filters.regex("name", "张");
FindIterable<Document> find = collection.find(eq);

查询的结果集映射,这种解析方式我们必须知道文档中的各个字段名

//查询
FindIterable<Document> find = collection.find();
//创建一个实体类集合准备接收结果
List<Student> slist = new ArrayList<Student>();
//获取结果集迭代器对象
MongoCursor<Document> iterator = find.iterator();
while(iterator.hasNext()) {
Student s = new Student();
Document next = iterator.next();
s.setSname(next.getString("name"));
s.setSex(next.getString("sex"));
s.setAge(next.getInteger("age"));
//将结果添加至实体类集合
slist.add(s);
}

释放资源

mongoclient.close();

Spring封装的MongoTemplate

导入Maven依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

配置application.yml

spring:
data:
mongodb:
uri: mongodb://localhost:27017/testdb

定义实体类

@Document(collection = "users")
public class User {
@Id
private String id;
private String name;
private int age;

// getters and setters
}

使用MongoTemplate:

@Autowired
private MongoTemplate mongoTemplate;

// 插入
mongoTemplate.insert(new User(null, "李四", 28));

// 查询
List<User> list = mongoTemplate.find(
Query.query(Criteria.where("age").gt(25)),
User.class
);

// 更新
mongoTemplate.updateFirst(
Query.query(Criteria.where("name").is("李四")),
Update.update("age", 30),
User.class
);

// 删除
mongoTemplate.remove(
Query.query(Criteria.where("name").is("李四")),
User.class
);

MongoTemplate提供的常用API

类名 作用
Query 构造一个查询条件对象,封装所有 Criteria 条件、分页、排序等信息
Criteria 构造查询条件,类似SQL的 WHERE 子句
Update 构造更新操作,类似SQL的 SETINCPUSH
Aggregation 构造聚合操作管道,等价于MongoDB的 $match$group$project
AggregationResults 聚合结果封装类,返回聚合结果

Criteria

Criteria.where("field")              // 指定字段
Criteria.is(value) // 等于
Criteria.ne(value) // 不等于
Criteria.gt(value) // 大于
Criteria.gte(value) // 大于等于
Criteria.lt(value) // 小于
Criteria.lte(value) // 小于等于
Criteria.in(list) // 包含
Criteria.nin(list) // 不包含
Criteria.regex("pattern") // 正则
Criteria.and("field2").is(value2) // 多条件拼接
Criteria.orOperator(...criteria) // OR 多条件

// 示例
Criteria criteria = Criteria.where("age").gte(18).lte(30).and("name").regex("^张");

Query

// 排序和分页
query.with(Sort.by(Sort.Direction.DESC, "createdTime"));
query.skip(20).limit(10); // 分页(跳过20条,取10条)

// 示例
Query query = new Query(Criteria.where("status").is("active"))
.with(Sort.by("updateTime").descending())
.limit(5);

Update

方法 功能
set(key, val) 设置字段值(覆盖)
unset(key) 删除字段
inc(key, val) 自增/自减
push(key, val) 向数组追加元素
pull(key, val) 从数组中移除元素
addToSet(key, val) 向数组追加且去重

示例:

Update update = new Update()
.set("name", "李四")
.inc("score", 10)
.unset("deprecatedField")
.addToSet("tags", "java");

Aggregation

MongoDB 操作符 Spring 方法 SQL
$match Aggregation.match() WHERE
$group Aggregation.group() GROUP BY
$project Aggregation.project() SELECT
$sort Aggregation.sort() ORDER BY
$limit Aggregation.limit() LIMIT
$skip Aggregation.skip() OFFSET
$lookup Aggregation.lookup() JOIN
$unwind Aggregation.unwind() 拆分数组字段
示例

统计近30天内已支付订单的总金额,按照用户ID分组,取出金额最高的前5名

Mongo原始聚合结构:

db.orders.aggregate([
{ $match: { status: "paid", createdTime: { $gte: ISODate(...) } } },
{ $group: { _id: "$userId", totalAmount: { $sum: "$amount" } } },
{ $sort: { totalAmount: -1 } },
{ $limit: 5 }
])

MongoTemplate:

public List<Document> top5UsersByPaidAmount() {
Date thirtyDaysAgo = LocalDate.now().minusDays(30)
.atStartOfDay(ZoneId.systemDefault())
.toInstant()
.let(Date::from);

// 构造聚合管道
Aggregation agg = Aggregation.newAggregation(
Aggregation.match(
Criteria.where("status").is("paid")
.and("createdTime").gte(thirtyDaysAgo)
),
Aggregation.group("userId")
.sum("amount").as("totalAmount"),
Aggregation.sort(Sort.by(Sort.Direction.DESC, "totalAmount")),
Aggregation.limit(5)
);

// 执行聚合
AggregationResults<Document> results =
mongoTemplate.aggregate(agg, "orders", Document.class);

return results.getMappedResults(); // List<Document>,每个包含 userId, totalAmount
}

常见问题

mongo为什么支持海量数据的快速检索

  • 文档模型(BSON)天生适合嵌套数据,比如一个用户的订单、地址、标签等信息可以嵌入在一个文档中,不需要多表关联
  • Mongo支持分片机制,可以将数据按特定键分布到不同的节点
  • Mongo支持多种索引:单字段索引、复合索引、全文索引、哈希索引、地理位置索引等
  • Mongo使用WiredTiger存储引擎,使用内存映射文件提升I/O性能,内存管理更精细,支持写时复制
  • Mongo对事务的管理默认是最终一致性,牺牲部分事务特性 → 写入吞吐更高

mongo如何实现分页查询

虽然Mongo是文档数据库,但是每条文档在存储时都会有一个唯一的_id字段,具备全局唯一、时间有序的特性。而且MongoDB的存储底层是B+树索引结构,所以数据可以根据索引字段排序和检索

mongo中的索引怎么建立的

MongoDB会自动为集合中的_id字段创建一个唯一索引

其他索引需要手动建立,可以在实体类中指定:

@Document(collection = "user")
@CompoundIndex(name = "name_age_idx", def = "{'name': 1, 'age': 1}")
public class User {
@Id
private String id;

private String name;
private Integer age;

@Indexed(unique = true)
private String email;
}

也可以用MongoTemplate动态创建索引:

mongoTemplate.indexOps(User.class)
.ensureIndex(new Index().on("name", Sort.Direction.ASC).on("age", Sort.Direction.DESC));

mongo索引与sql索引对比

相同点:

类别 说明
提高查询性能 无论是SQL还是MongoDB,索引的核心目的是提高检索效率、避免全表扫描(SQL)或全集合扫描(Mongo)
支持多种类型 都支持单字段索引、复合索引、多值字段索引(如PostgreSQL的GIN,Mongo的多键索引)等
唯一约束 都可以建立唯一索引,防止插入重复的数据
支持排序优化 查询中使用 ORDER BY / sort 的字段建立索引可提升排序效率
索引代价 都会增加写入成本(插入/更新数据时需维护索引),占用存储空间

不同点:

方面 SQL 索引 MongoDB 索引
数据模型 行和列(表结构) 文档(JSON/BSON)
存储结构 多为B+树 默认使用B树,新版引擎使用B+树
索引字段 文档中的字段,包括嵌套字段
多值字段支持 通常不支持(除非特殊类型,如数组列+GIN索引) 支持数组字段(自动建立多键索引)
建立方式 SQL语句CREATE INDEX Java中用API或Mongo Shell:db.collection.createIndex()
聚合优化 依赖执行计划优化器选择索引 在Aggregation Pipeline中可通过 $match + $sort 等操作触发索引使用
文本索引 MySQL等需特殊配置 内建支持 $text 索引,支持全文搜索
聚集索引 可以是聚集,也可以非聚集 默认非聚集