phase 5 part 3: mongodb, elasticsearch hybrid search, data-pipeline, nodejs BFF topics with verified examples
This commit is contained in:
1 parent
9eeb10aede
commit
075872e31e
27 files changed
+2405
-52
No files matched your search
@@ -0,0 +1,105 @@
|
||||
---
|
||||
id: backend.mongodb.document-model
|
||||
title: MongoDB 基础:文档建模(嵌入还是引用)、索引与 ESR、聚合、事务和变更流
|
||||
type: lesson
|
||||
domain: backend
|
||||
stack: [mongodb]
|
||||
tags: [MongoDB, 文档模型, 嵌入, 引用, 复合索引, ESR, 聚合管道, 事务, 变更流, 副本集]
|
||||
level: basic
|
||||
prerequisites: [backend.postgresql.index]
|
||||
est_hours: 3
|
||||
related: [backend.mongodb.scenario.conversation-store, backend.elasticsearch.index, backend.mongodb.interview]
|
||||
week: [14]
|
||||
order: 1
|
||||
---
|
||||
|
||||
# MongoDB 基础:文档建模(嵌入还是引用)、索引与 ESR、聚合、事务和变更流
|
||||
|
||||
> BOSS直聘 2026-10-23 的调研中,有岗位把 MongoDB 列为要求或加分项(常见于存储对话记录、日志、灵活结构的业务数据)。定位:**了解并能用**,主库仍以 PostgreSQL / MySQL 为主。示例基于 MongoDB 8.2 + PyMongo 4.19 实际运行通过;以官方文档为准:https://www.mongodb.com/docs/manual/ 。
|
||||
|
||||
## 解释说明
|
||||
|
||||
### 和关系型数据库的对应
|
||||
|
||||
| 关系型 | MongoDB |
|
||||
| --- | --- |
|
||||
| 数据库 / 表 / 行 | database / collection / document(BSON,类似 JSON) |
|
||||
| 表结构 | 默认无固定结构,可用 `$jsonSchema` 校验 |
|
||||
| JOIN | 优先“嵌入”,必要时用聚合的 `$lookup` |
|
||||
| 事务 | 单文档写入天然原子;多文档事务需要副本集或分片集群 |
|
||||
|
||||
### 建模:嵌入还是引用
|
||||
|
||||
- **嵌入**:总是一起读写、数量有上限的子数据(会话中的最近消息、订单中的商品快照)。一次读取拿到全部,单文档原子更新。
|
||||
- **引用**:数量无上限或会被独立访问、多处共享的数据(用户、商品主数据、海量消息)。
|
||||
- 单个文档上限 16MB,数组不能无限增长(“无限数组”是常见反模式)。
|
||||
|
||||
### 索引
|
||||
|
||||
- 单字段、复合、多键(数组)、TTL(自动过期)、唯一、文本索引等。
|
||||
- **复合索引的 ESR 规则**:等值条件字段在前(Equality),排序字段居中(Sort),范围条件字段在后(Range)。
|
||||
- 用 `explain()` 看是否 `IXSCAN`(走索引)、有没有 `COLLSCAN`(全表扫描)和 `SORT`(内存排序)。
|
||||
|
||||
### 聚合管道
|
||||
|
||||
`$match` → `$unwind` → `$group` → `$sort` → `$project` …… 像流水线一样处理;`$match` 尽量放在最前面以利用索引。
|
||||
|
||||
### 副本集、事务、变更流
|
||||
|
||||
- **副本集**:一主多从,自动故障转移;读写关注(`readConcern` / `writeConcern`)控制一致性。
|
||||
- **事务**:多文档 ACID,需要副本集;有性能成本,优先通过建模让一次写入只改一个文档。
|
||||
- **变更流(Change Streams)**:订阅集合的变更,常用于同步到 ES / 缓存 / 消息队列,支持 resume token 断点续传。
|
||||
|
||||
## 案例
|
||||
|
||||
AI 客服会话存储:会话文档嵌入最近消息(上限 200 条),结构随渠道不同而变化;按“租户 + 状态 + 时间倒序”查询待处理会话;转人工时同时改会话状态并创建工单(事务);变更流把工单同步到搜索服务。
|
||||
|
||||
## 代码示例
|
||||
|
||||
[examples/conversations/mongo_demo.py](examples/conversations/mongo_demo.py)(需要 `MONGO_URL`,单节点副本集即可),实际输出:
|
||||
|
||||
```text
|
||||
1. Schema 校验:非法 status 被拒绝
|
||||
2. 查询走索引且无需内存排序:['FETCH', 'IXSCAN', 'LIMIT']
|
||||
3. 聚合统计 6 组,例如 {'tenant': 't0', 'channel': 'app'} 共 50 个会话,平均 22.9 tokens
|
||||
4. 幂等 upsert:重复投递 3 次只有 1 条
|
||||
5. 事务:成功的提交生效,失败的整体回滚
|
||||
6. 变更流收到插入事件,并拿到 resume token 用于断点续传
|
||||
```
|
||||
|
||||
ESR 索引和 explain:
|
||||
|
||||
```python
|
||||
conv.create_index([("tenant_id", 1), ("status", 1), ("created_at", -1)]) # 等值、等值、排序
|
||||
plan = conv.find({"tenant_id": "t1", "status": "handoff"}).sort("created_at", -1).limit(10).explain()
|
||||
```
|
||||
|
||||
幂等 upsert:
|
||||
|
||||
```python
|
||||
events.create_index("event_id", unique=True)
|
||||
events.update_one({"event_id": "evt-001"}, {"$setOnInsert": {"payload": "退款申请"}}, upsert=True)
|
||||
```
|
||||
|
||||
本地启动单节点副本集(无 Docker 时可用官方二进制包):
|
||||
|
||||
```bash
|
||||
mongod --dbpath ./data --replSet rs0 --bind_ip 127.0.0.1 --port 27017
|
||||
mongosh --eval 'rs.initiate()' # 或用 PyMongo 执行 replSetInitiate
|
||||
```
|
||||
|
||||
## 易错点
|
||||
|
||||
- 把关系型的表结构原样搬过来,到处 `$lookup`,失去文档模型的优势。
|
||||
- 无限增长的嵌入数组。
|
||||
- 以为“无结构”就不需要校验:关键字段要用 Schema 校验和应用层校验。
|
||||
- 复合索引顺序不对,查询仍然内存排序。
|
||||
- 单机(非副本集)部署上使用事务或变更流,直接报错。
|
||||
- 用 `skip` 做深分页:越往后越慢,改用“基于上一页最后一个值”的范围查询。
|
||||
|
||||
## 面试常问
|
||||
|
||||
- 什么时候选 MongoDB,什么时候选 PostgreSQL?
|
||||
- 嵌入和引用怎么选?
|
||||
- 复合索引的设计原则?
|
||||
- MongoDB 支持事务吗?有什么限制?
|
||||
Reference in new issue
Block a user