MongoDB 事务指南
概述
monSQLize 在 MongoDB driver session 之上提供 withTransaction() 与 startSession() 辅助方法。ACID 边界来自 MongoDB 事务/session;monSQLize 额外负责重试、超时、统计和缓存失效协调。
核心特性
- ✅ 自动事务管理(withTransaction - 推荐)
- ✅ 手动事务管理(startSession - 高级用法)
- ✅ 事务感知缓存策略(带 session 的读绕过共享查询缓存,写入记录 commit 后失效)
- ✅ 进程内缓存屏障/锁(围绕缓存失效提供短窗口保护)
- ✅ 自动重试(瞬态错误自动重试)
- ✅ 超时处理(自动中断长事务)
- ✅ 监控指标(执行时长、成功率等)
- ✅ 读关注/读偏好/因果一致性
前置要求
- ✅ MongoDB 4.0+ 副本集或分片集群
- ✅ Node.js 18+
快速开始
1. 初始化与配置
2. 使用自动事务(推荐⭐)
最简单的方式,自动管理提交、回滚和重试:
3. 使用手动事务(高级用法)
需要精细控制事务生命周期时使用:
配置选项
全局配置(构造函数)
单次事务配置
API 参考
msq.withTransaction(callback, options)
自动管理事务(推荐)。
参数:
callback(tx): 事务回调函数tx.session: MongoDB session 对象tx.id: 事务唯一标识tx.state: 事务状态('pending' | 'active' | 'committed' | 'aborted')
options: 事务选项(可选)
返回: Promise
示例:
msq.startSession(options)
创建手动事务会话。
参数:
options: 事务选项(同 withTransaction)
返回: Promise
Transaction 实例方法:
start(): 开始事务commit(): 提交事务abort(): 回滚事务end(): 释放资源
示例:
缓存策略
⭐ 默认策略:事务内不缓存(推荐)
设计理念: 事务追求数据一致性,缓存追求性能。默认情况下,事务内操作不使用缓存,确保数据准确性。
Session 读与缓存
当操作带有 session: tx.session 时,monSQLize 会把 session 透传给 MongoDB driver,并跳过共享查询结果缓存。当前公开 API 中没有单独的事务缓存隔离开关。事务内重复读取应依赖 driver snapshot;如果某些读可以安全缓存,应放到事务外执行。
缓存锁机制(配置失效时)
作用: 在事务期间记录显式缓存失效意图,并在 commit 后 flush 或 abort 丢弃之前提供短窗口的进程内屏障。
缓存锁是进程内机制。跨实例缓存收敛依赖 commit 后的分布式失效广播,且属于 best-effort;需要跨进程互斥的关键路径应在应用/框架层显式协调。
缓存策略对比
最佳实践
1. 幂等性设计 ⭐
重要: 事务回调必须幂等,因为可能自动重试。
2. 超时时间设置
3. 错误处理
4. 性能优化
5. 监控与日志
常见问题
Q1: 为什么事务内查询没有使用缓存?
A: 这是设计的默认行为。原因:
- 数据一致性优先 - 事务追求准确性,缓存可能有延迟
- 避免脏读 - 事务内应该读取最新数据
- 简化使用 - 用户不需要考虑缓存问题
如果需要性能优化,可以显式启用 cache 选项。
Q2: 什么时候使用手动事务?
A: 大多数情况使用 withTransaction(自动)。以下情况考虑手动:
- 需要在事务开始前做复杂判断
- 需要在 commit 前做额外验证
- 需要细粒度控制事务生命周期
Q3: 事务失败如何调试?
A: 检查以下几点:
- MongoDB 是副本集吗? - 单节点不支持事务
- 连接字符串正确吗? - 需要
?replicaSet=rs0 - 超时时间合理吗? - 默认30秒
- 回调是否幂等? - 可能重试多次
Q4: 缓存锁会影响性能吗?
A: 影响很小。原因:
- 锁仅在事务执行期间生效(通常很短)
- 锁是进程内内存级别的(不涉及网络 I/O)
- 锁的检查非常快(O(1) 哈希查找)
Q5: 事务内可以操作多个数据库吗?
A: 可以,但有限制:
- ✅ 同一个 MongoDB 集群内的多个数据库
- ❌ 跨 MongoDB 集群(不支持)
Q6: 并发事务会死锁吗?
A: MongoDB 会自动检测并抛出 WriteConflict 错误。monSQLize 会自动重试(如果启用了 enableRetry)。