这是《MongoDB 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 MongoDB 的一致性由写关注、读关注和读偏好共同决定。理解三者组合,才能回答“这条数据能不能读到”“读到的是否是多数已提交”“是否可能比 Primary 旧”。
20.1 三层控制
| 机制 | 问题 |
|---|---|
| Write Concern | 写到多少节点才返回成功 |
| Read Concern | 读哪个一致性级别的数据 |
| Read Preference | 从哪个成员读 |
示例:
db.orders.find(
{ user_id: "u_1001" }
).readPref("primary").readConcern("majority")
20.2 Write Concern
写关注:
db.orders.insertOne(
{ _id: "o_1", status: "CREATED" },
{ writeConcern: { w: "majority", j: true, wtimeout: 3000 } }
)
| 参数 | 说明 |
|---|---|
w: 0 |
不等待确认,风险高 |
w: 1 |
Primary 确认 |
w: "majority" |
多数成员确认 |
j: true |
等待 journal |
wtimeout |
超时时间 |
重要数据建议 majority;日志、行为事件可按成本选择,但要有补偿和对账。
20.3 Read Concern
常见级别:
| 级别 | 语义 |
|---|---|
| local | 返回本节点已应用的数据 |
| majority | 返回多数节点已提交的数据 |
| linearizable | 强一致线性读 |
| snapshot | 事务快照 |
| available | 可用性优先,分片场景语义需注意 |
local 可能读到后续回滚的数据;majority 降低回滚读风险;linearizable 延迟和限制更高。
20.4 Read Preference
| 模式 | 行为 |
|---|---|
| primary | 只读 Primary |
| primaryPreferred | 优先 Primary |
| secondary | 只读 Secondary |
| secondaryPreferred | 优先 Secondary |
| nearest | 最低延迟成员 |
Secondary 读不等于弱一致,最终语义还要看 read concern;但复制延迟会让数据版本较旧。
20.5 因果一致性
客户端会话可以维护因果令牌,让后续操作看到之前操作的影响:
const session = db.getMongo().startSession({ causalConsistency: true });
const orders = session.getDatabase("shop").orders;
orders.insertOne({ _id: "o_1", status: "CREATED" });
orders.find({ _id: "o_1" });
session.endSession();
适用:
- 写后读;
- 会话内单调读;
- 分页延续;
- 多步配置变更。
因果一致性不等于全局线性一致。
20.6 写后读场景
支付后立即查询:
write majority
-> read primary + majority
配置:
db.orders.insertOne(
{ _id: "o_1", status: "PAID" },
{ writeConcern: { w: "majority" } }
)
db.orders.find({ _id: "o_1" }).readPref("primary")
如果使用 Secondary 读,用户可能看不到刚支付的状态,需要产品提示或强制 Primary。
20.7 报表读取
报表可以读 Hidden Secondary:
read preference: secondary
read concern: majority
特点:
- 不影响主业务 Primary;
- 可能滞后;
- 重查询仍会影响该 Secondary;
- 应有资源隔离和超时;
- 跨分片聚合由 mongos 合并。
20.8 分片一致性
分片下还要注意:
- 不带分片键的查询可能读取多个 shard;
- 各 shard 复制进度可能不同;
- majority 读降低回滚风险;
- 事务需要快照和提交协调;
- mongos 拓扑状态影响路由。
对账和全局报表应明确时间点。
20.9 一致性权衡
| 需求 | 建议 |
|---|---|
| 支付状态 | primary + majority |
| 用户资料 | primary 或 secondaryPreferred |
| 报表 | secondary + majority |
| 推荐缓存 | nearest + local |
| 审计追踪 | majority 写入 |
| 跨服务流程 | 业务状态机 + 对账 |
不要为所有请求使用最高一致性,也不要默认所有 Secondary 读都能接受。
20.10 超时与重试
写关注超时后,写入可能已经发生:
timeout -> unknown result
处理方式:
- 保存请求 ID;
- 查询业务状态;
- 幂等重试;
- 记录不确定结果;
- 告警;
- 不盲目重复不可幂等写入。
本章小结
一致性不是单一参数,而是写关注、读关注和读偏好的组合。核心交易使用 majority 与 Primary 读,分析报表可以读 Secondary,但仍要定义滞后容忍度。所有超时都要按“结果未知”处理。
思考题
w: 1和w: majority的核心差异是什么?- local 读可能读到什么风险数据?
- Read Preference 是否决定数据提交级别?
- 因果一致性适合什么场景?
- 写关注超时后为什么不能直接认定失败?