一致性与事务
MongoDB 的一致性不是单一开关,而是由单文档原子性、Write Concern、Read Concern、Read Preference、Session 和事务共同决定。只说“MongoDB 支持事务”无法说明一次读写实际获得的保证。
单文档原子性
对单个文档的写操作是原子的,即使一次更新修改多个嵌套 Field。把必须共同变化的数据放进同一文档,可以不启用多文档事务:
javascript
db.accounts.updateOne(
{ _id: accountId, balance: { $gte: 100 } },
{ $inc: { balance: -100 }, $push: { entries: entry } }
)Filter 同时承担并发前置条件。应用必须检查 matchedCount 和 modifiedCount,不能只根据请求没有抛异常就认定业务更新成功。
Write Concern
Write Concern 定义写操作需要获得什么级别的确认:
| 设置 | 含义 | 风险与成本 |
|---|---|---|
w: 1 | Primary 确认接收写入 | Primary 故障时尚未复制的写可能回滚 |
w: "majority" | 多数投票成员确认写入 | 延迟受多数派网络和磁盘影响 |
j: true | 等待相关节点的 Journal 持久化 | 提升节点崩溃后的持久性,增加确认延迟 |
wtimeout | 限制等待确认时间 | 超时表示确认条件未满足,不必然代表写入未发生 |
客户端超时或网络断开后,写入状态可能未知。可重试写、幂等键和业务去重用于处理“请求结果未知”,不能简单地无条件再次插入。
Read Concern 与 Read Preference
Read Concern 决定读到什么一致性级别;Read Preference 决定去哪个复制集成员读取。二者不能互相替代。
| Read Concern | 主要语义 |
|---|---|
local | 返回当前节点可见的数据,故障切换后可能回滚 |
majority | 返回多数派已提交的数据 |
linearizable | 对 Primary 上单文档读取提供更强实时顺序保证,限制和成本较高 |
snapshot | 事务或受支持场景中的一致时间点视图 |
从 Secondary 读取可能降低 Primary 读压力或改善地域延迟,但数据可能落后。关键业务的“刚写完立即读到”需要结合 Session、因果一致性、Read Preference 与 Concern 明确设计。
多文档事务
javascript
const session = db.getMongo().startSession()
const accounts = session.getDatabase("bank").accounts
session.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
})
try {
accounts.updateOne({ _id: from }, { $inc: { balance: -100 } })
accounts.updateOne({ _id: to }, { $inc: { balance: 100 } })
session.commitTransaction()
} catch (error) {
session.abortTransaction()
throw error
} finally {
session.endSession()
}事务具有超时、锁、Cache、Oplog 和重试成本。驱动通常提供带重试规则的 Transaction Callback API,生产代码应按驱动规范处理 Transient Transaction Error 和 Unknown Commit Result。
多文档事务适合无法通过聚合边界消除的少量强一致变更,不应成为照搬关系模型的默认工具。跨 Shard 事务还会引入分布式协调。