MongoDB - 原子操作
MongoDB - 原子操作
Section titled “MongoDB - 原子操作”原子性(Atomicity)确保操作要么完全完成,要么根本不发生,从而防止数据不一致。在 MongoDB 中,对单个文档的操作始终是原子性的。本教程将探讨如何利用这一点实现可靠的数据更新,并介绍现代的多文档事务。
策略一:通过嵌入式文档实现单文档原子性
Section titled “策略一:通过嵌入式文档实现单文档原子性”对于频繁更新的相关数据,最佳实践是将其嵌入到单个文档中。这利用了 MongoDB 原生的单文档原子性。当您更新文档时,其所有嵌入字段都在一个原子操作中完成更新。
我们以一个电子商务库存场景为例。我们将在一个文档中跟踪产品的库存及其购买历史。
// 首先,我们向 'products' 集合中插入一个产品db.products.insertOne({ "_id": "SKU12345", "productName": "Quantum Laptop Pro", "category": "Electronics", "stock": { "total": 10, "available": 10 }, "purchaseHistory": []});// 返回值: { "acknowledged": true, "insertedId": "SKU12345" }当顾客购买此笔记本电脑时,我们需要原子性地减少可用库存并记录购买。这可以防止两个顾客同时购买最后一件商品而产生的竞态条件。我们使用 findOneAndUpdate() 来实现此目的,因为它在一次原子性的服务器调用中查找并更新文档。
// 如果库存可用,则原子性地处理购买db.products.findOneAndUpdate( // 过滤器:通过 ID 查找产品并确保库存可用 { _id: "SKU12345", "stock.available": { $gt: 0 } }, // 更新:减少可用库存并在购买历史中添加新条目 { $inc: { "stock.available": -1 }, $push: { "purchaseHistory": { "customerId": "CUST987", "purchaseDate": new Date() } } }, // 选项:返回更新后的文档以确认更改 { returnNewDocument: true });这个单一操作保证了只有在库存可用时才减少库存,并且只有在库存成功减少时才记录购买。整个更改是原子性的。
策略二:多文档 ACID 事务
Section titled “策略二:多文档 ACID 事务”如果某个操作需要原子性地更新多个文档怎么办?例如,将资金从一个用户账户转移到另一个账户。从 MongoDB 4.0 开始,您可以为这些场景使用多文档 ACID 事务。
事务通过会话(session)对象处理。事务中的所有操作都会被暂存,然后作为一个单一的原子单元被提交(应用)或中止(丢弃)。
以下是银行转账的一个概念性示例:
// 注意:这需要副本集配置。
// 1. 启动客户端会话。const session = db.getMongo().startSession();
// 2. 启动事务。session.startTransaction();
const accountsCollection = session.getDatabase('bank').collection('accounts');
try { // 3. 在事务中执行操作。 // 从发件人账户扣款 accountsCollection.updateOne( { _id: 'accountA' }, { $inc: { balance: -100 } }, { session } );
// 向接收人账户存款 accountsCollection.updateOne( { _id: 'accountB' }, { $inc: { balance: 100 } }, { session } );
// 4. 提交事务。 session.commitTransaction(); console.log('事务成功提交。');
} catch (error) { // 5. 如果发生任何错误,则中止事务。 console.log('发生错误。中止事务。'); session.abortTransaction(); throw error;
} finally { // 6. 结束会话。 session.endSession();}当您的数据结构允许时,始终优先选择单文档原子模型。它比多文档事务性能更高且复杂性更低。仅当跨多个文档的原子性是绝对必要时才使用事务。