MongoDB 支持哪些原子操作?
MongoDB 对原子操作的支持主要分为三个层面:单文档原子性(核心机制)、原子操作符以及多文档事务(ACID)。
以下是详细的分类说明:
1. 单文档原子性 (Single Document Atomicity)
这是 MongoDB 最基础也是最重要的特性。在 MongoDB 中,针对单个文档(Document)的所有写操作(insert, update, delete)都是原子的。
即使一个 update 操作需要修改该文档内的多个字段(例如同时修改嵌套数组和顶层字段),这个操作要么全部成功,要么全部失败,不会出现只修改了一半的情况。
2. 原子更新操作符 (Atomic Update Operators)
为了在修改单文档时实现复杂的逻辑(如计数、数组操作),MongoDB 提供了一系列原子操作符。这些操作符在并发环境下非常有用,避免了“读取-修改-写入”带来的竞态条件。
字段修改类:
$set: 设置字段的值。$unset: 删除字段。$inc: 对数值字段进行原子增减(常用于计数器,无需先读后写)。$mul: 将字段值乘以指定数值。$rename: 重命名字段。$currentDate: 设置字段为当前时间。
数组修改类:
$push: 向数组追加元素。$pop: 移除数组头部或尾部的元素。$pull: 移除数组中匹配特定条件的元素。$addToSet: 仅当元素不存在时才向数组添加(去重添加)。$pullAll: 移除数组中所有指定的元素。
位运算类:
$bit: 对整型字段进行位运算(AND, OR, XOR)。
3. findAndModify 类操作 (原子读写)
这是一个非常强大的功能,它允许你在一个原子操作中完成“查询”和“更新/删除”,并返回更新前或更新后的文档。这对于实现任务队列、分布式锁或序列生成器至关重要。
findOneAndUpdate(): 查找文档,修改它,并返回(修改前或修改后的)文档。findOneAndReplace(): 查找文档,完全替换它,并返回文档。findOneAndDelete(): 查找文档,删除它,并返回被删除的文档。
4. 多文档事务 (Multi-Document Transactions)
从 MongoDB 4.0 开始(副本集)和 4.2(分片集群),MongoDB 支持跨多个文档、多个集合甚至多个数据库的 ACID 事务。
- 这意味着你可以像在关系型数据库(如 MySQL)中一样,使用
startTransaction、commitTransaction和abortTransaction。 - 在事务块内的所有操作,要么全部提交,要么全部回滚,对外表现为原子性。
- 注意:虽然支持,但 MongoDB 官方建议优先通过合理的数据建模(如嵌入式文档)利用单文档原子性,因为多文档事务会有一定的性能开销。
5. 批量操作 (Bulk Write Operations)
使用 bulkWrite() 可以执行一组写操作。
- 有序 (Ordered - 默认): 按顺序执行。如果中间某个操作失败,后续操作停止。虽然整体不是原子的(除非放在事务中),但每个单独的操作是原子的。
- 无序 (Unordered): 并行执行。如果某个操作失败,其他操作继续。
6. 并发控制模式 (CAS - Compare And Swap)
虽然这不是一个具体的命令,但利用单文档原子性,MongoDB 经常被用来实现乐观锁(Optimistic Locking):
javascript
// 只有当 version 等于读取到的值时才更新,实现原子性的“检查并设置”
db.collection.updateOne(
{ _id: 1, version: 5 },
{
$set: { status: "active" },
$inc: { version: 1 }
}
)
总结
| 操作层级 | 描述 | 典型场景 |
|---|---|---|
| 单文档 | 原生支持,默认原子性 | 绝大多数日常 CRUD 操作 |
| 字段/数组 | $inc, $push 等操作符 |
计数器、标签管理、点赞数 |
| 读写混合 | findAndModify |
任务队列领取、状态流转 |
| 多文档 | 事务 (Transactions) | 银行转账、订单库存扣减 (跨集合) |