1. MongoDB 与传统关系型数据库的区别?
MongoDB 是一种 NoSQL 的文档型数据库,和传统关系型数据库(如 MySQL、PostgreSQL)在以下几个方面存在显著差异:
-
- 数据模型
MongoDB:以 BSON 文档形式存储数据,结构灵活,字段可以嵌套对象和数组,天然适合非结构化或半结构化数据。
- 数据模型
关系型数据库:采用表结构,字段和类型固定,严格遵守行与列的二维模型,数据之间通过外键等方式关联。
-
- 模式(Schema)
MongoDB:默认是 schema-less(无固定模式),可以存储结构不同的文档;也可以通过 JSON Schema 进行约束。
- 模式(Schema)
关系型数据库:必须先定义表结构(字段、类型、约束等),插入数据必须符合表定义。
-
- 查询语言
MongoDB:使用类 JSON 的查询语法,操作方式更接近编程对象操作。
- 查询语言
关系型数据库:使用标准的 SQL 语言,具备强大的 JOIN、子查询、事务等能力。
2. MongoDB 的数据模型和基本结构?
MongoDB 是一个基于文档的 NoSQL 数据库,核心的数据模型是「数据库 → 集合 → 文档」的三层结构:
-
- 数据库(Database):就像关系型数据库中的一个库。
- 集合(Collection):类似表,但不需要提前定义字段结构,可以存储结构不同的文档。
- 文档(Document):是实际存储的数据单元,格式是类 JSON 的 BSON,可以包含嵌套对象和数组。
每个文档默认有一个 _id
主键字段,类型是 ObjectId
,也可以自定义。文档中的字段不要求统一,MongoDB 的 schema 是灵活的。
MongoDB 的集合可以隐式创建,也可以使用 db.createCollection()
显式创建;一般情况下是自动创建的,但如果需要添加 schema 验证或者配置固定集合(如 capped collection),就会使用显式方式。
这种模型非常适合处理非结构化、变化频繁的数据,比如内容管理系统、用户行为日志等场景。
3. MongoDB 如何实现主键自增?
MongoDB 默认使用 ObjectId 作为 _id
主键,是全局唯一但不自增。如果需要自增主键,可以在应用层用 Redis ,UUID 或雪花算法生成唯一 ID。
4. MongoDB 如何实现索引?常见索引类型有哪些?
MongoDB 支持多种索引类型,包括单字段、复合索引、唯一索引、文本索引、TTL索引、地理空间索引等。我们可以通过 createIndex()
创建索引,通过 explain()
查看是否命中。使用索引可以大幅提升查询性能,但也会带来写入开销,需要权衡使用。
- 索引会加快读取,但会增加写入开销(写时需要更新索引);
- 应避免滥用索引,尤其是重复索引、低选择性字段索引;
- 查询语句应尽量命中索引前缀(尤其是复合索引);
- 可通过
explain()
分析查询是否使用了索引; - MongoDB 的
_id
字段默认自带唯一索引。
5. MongoDB 如何进行聚合查询?
使用 Spring Data MongoDB 的 Aggregation API
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
@Service
public class SearchHistoryService {
@Autowired
private MongoTemplate mongoTemplate;
public List<UserSearchCount> aggregateSearchCountsLast7Days() {
Date sevenDaysAgo = new Date(System.currentTimeMillis() - 7L * 24 * 60 * 60 * 1000);
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(org.springframework.data.mongodb.core.query.Criteria.where("createdAt").gte(sevenDaysAgo)),
Aggregation.group("userId").count().as("count"),
Aggregation.sort(org.springframework.data.domain.Sort.Direction.DESC, "count"),
Aggregation.limit(10)
);
AggregationResults<UserSearchCount> results = mongoTemplate.aggregate(aggregation, "searchHistory", UserSearchCount.class);
return results.getMappedResults();
}
public static class UserSearchCount {
private String userId;
private int count;
// getters and setters
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
}
6. MongoDB 是否支持事务?实现机制是什么?
MongoDB 自 4.0 起支持多文档 ACID 事务,基于客户端会话和 WiredTiger 的 MVCC 实现,支持副本集和分片集群环境。分布式事务采用两阶段提交协议,保证跨分片操作的原子性和一致性。事务使用时需启动会话,写操作提交或回滚,性能开销较大,应慎重使用。
7. MongoDB 如何实现高可用?
MongoDB 通过副本集实现高可用,副本集包含主节点和多个从节点,主节点写入数据后同步到从节点;当主节点故障时,自动通过选举产生新的主节点,实现自动故障转移。结合分片集群,可以实现数据的高可用和水平扩展。
8. MongoDB 如何做分片?分片的原理是什么?
MongoDB 分片机制类似 Kafka 的分区概念,都是将数据划分为多个分区(分片),但 MongoDB 的分片是针对 collection 的数据做水平切分,每个分片本身是一个副本集,保证高可用。客户端通过 mongos 路由器把请求按 shard key 路由到对应分片,实现分布式存储和负载均衡。
|------------|-------------------------------|-----------------------------------|
| 方面 | MongoDB 分片(Sharding) | Kafka 分区(Partition) |
| 划分粒度 | 一个数据库中的每个 collection 可以分成多个分片 | 一个 topic 可以划分成多个分区 |
| 分片含义 | 分片是数据水平切分,分片内存储部分数据 | 分区是消息队列的基本单位,每个分区存储部分消息 |
| 副本机制 | 每个分片本身是一个副本集,分片有主节点和多个从节点 | 分区有一个 leader(主节点)和多个 follower(副本) |
| 数据分布方式 | 根据 shard key(分片键)决定数据路由到哪个分片 | 生产者根据 key 决定消息写入哪个分区 |
| 扩展性 | 通过增加分片节点实现水平扩展,分片数目固定不轻易变动 | 分区数在 topic 创建时定义,运行中不易变 |
| 主要目的 | 解决数据量大、读写压力高的问题,实现大规模分布式存储 | 实现高吞吐、高可用的消息传递 |
9. MongoDB 性能优化常见措施有哪些?
1.合理设计索引
建立常用查询字段的索引,避免全表扫描
利用复合索引覆盖多个查询条件
使用唯一索引保证数据唯一性和快速定位
避免索引过多,防止写入性能下降
利用TTL 索引自动过期数据,减小数据量
2.集群与硬件层面优化
使用副本集实现读写分离,读取从节点,减轻主节点压力
合理配置分片集群,实现水平扩展和负载均衡
3.优化查询
避免使用不支持索引的查询操作,如正则表达式前缀不确定的查询
使用 explain() 分析查询执行计划,发现慢查询
避免在大文档里返回不必要的字段,使用 投影(projection)减少数据传输
合理分页,避免大偏移量分页带来的性能损耗,推荐基于条件的"游标"分页
10. MongoDB 和 Elasticsearch 如何配合使用?
MongoDB 负责存储,Elasticsearch 负责搜索,通过同步机制保持数据一致,实现高性能、高可用的搜索架构。
典型流程示例:
用户发起搜索请求
前端将搜索关键词发送到后端。
ES 执行全文检索
后端调用 ES,快速定位相关文档(例如文章、商品等),并返回匹配结果。
MongoDB 记录搜索历史
将用户搜索关键词、时间戳、用户ID 等信息异步写入 MongoDB,方便后续做热搜、个性化推荐等。
MySQL 作为主数据源
存储业务核心数据,如用户信息、订单、权限等,确保数据完整性和事务一致。
方式 1:应用层双写(同步写入)
-
代码中同时写入 MongoDB 和 Elasticsearch。
-
优点:实现简单。
-
缺点:耦合高、失败处理复杂。
articleRepository.save(article); // MongoDB
esClient.index(articleDoc); // Elasticsearch
方式 2:异步同步(推荐)
-
写入 MongoDB 后发送事件(Kafka、RabbitMQ、RocketMQ)
-
消费者异步同步数据到 Elasticsearch。
-
优点:解耦、性能好、可靠性强。
用户发请求 → MongoDB 存数据 → 发送同步事件 → 消费者写入 Elasticsearch
11. 搜索框的历史搜索记录,用 MongoDB 存储有什么好处?
-
MongoDB 支持灵活字段、嵌套对象和数组,无需修改表结构即可扩展字段,开发迭代效率高。
-
MongoDB 的写入性能优于传统关系型数据库,特别适合高并发、高速写入场景。搜索记录属于典型的"写多读少。
-
MongoDB 支持 TTL 索引,可以自动删除过期数据,免维护清理脚本。
db.searchHistory.createIndex(
{ createdAt: 1 }, // 对 createdAt 字段创建升序索引
{ expireAfterSeconds: 2592000 } // 设置文档在 createdAt 时间点后 2592000 秒自动过期(30 天)
)
12. 如何在项目中使用 MongoDB?
-
添加依赖(Maven)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> -
配置
application.properties
spring.data.mongodb.uri=mongodb://localhost:27017/mydb
-
定义实体类
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;@Document(collection = "users")
public class User {
@Id
private String id;
private String name;
private Integer age;// getters and setters
}
-
定义 Repository 接口
import org.springframework.data.mongodb.repository.MongoRepository;
public interface UserRepository extends MongoRepository<User, String> {
User findByName(String name);
} -
使用示例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserService {
@Autowired
private UserRepository userRepository;public void runExample() { User user = new User(); user.setName("Alice"); user.setAge(25); userRepository.save(user); User found = userRepository.findByName("Alice"); System.out.println(found.getName() + ", " + found.getAge()); }
}