如何在NodeJS/TypeScript中原子更新Mongo文档的lastUpdatedTimestamp
在Node.js/TypeScript中实现MongoDB文档的条件原子更新(基于lastUpdatedTimestamp)
要解决这个竞态条件问题,核心是利用MongoDB的原子更新操作,把「时间戳对比」和「更新时间戳」合并为一个服务器端执行的原子命令,避免客户端读取后到写入前的中间状态被其他请求篡改。
下面分别给出基于原生MongoDB驱动和Mongoose两种常用方式的实现:
1. 基于原生MongoDB驱动实现
首先确保已安装依赖包:
npm install mongodb @types/mongodb
实现代码:
import { MongoClient, ObjectId } from 'mongodb'; // 请求参数类型定义 interface UpdateRequest { docId: string; clientLastUpdatedTimestamp: number; // 客户端传来的毫秒级时间戳 } async function updateDocumentIfTimestampMatches(req: UpdateRequest) { const client = new MongoClient('mongodb://localhost:27017'); try { await client.connect(); const db = client.db('your-database-name'); const collection = db.collection('your-collection-name'); // 原子更新:仅当数据库中lastUpdatedTimestamp与请求值匹配时才执行更新 const result = await collection.updateOne( { _id: new ObjectId(req.docId), lastUpdatedTimestamp: req.clientLastUpdatedTimestamp // 匹配条件 }, { $set: { lastUpdatedTimestamp: Date.now() // 更新为当前毫秒级时间戳 } } ); // 判断更新结果 if (result.modifiedCount === 1) { return { success: true, message: '文档更新成功' }; } else { return { success: false, message: '时间戳不匹配,文档未更新(可能已被其他请求修改)' }; } } finally { await client.close(); } }
2. 基于Mongoose实现
如果使用Mongoose(更适配TypeScript类型约束),先安装依赖:
npm install mongoose @types/mongoose
实现代码:
import mongoose from 'mongoose'; // 定义文档的TypeScript接口与Schema interface YourDocument extends mongoose.Document { lastUpdatedTimestamp: number; // 其他业务字段... } const YourSchema = new mongoose.Schema<YourDocument>({ lastUpdatedTimestamp: { type: Number, required: true } // 其他字段定义... }); const YourModel = mongoose.model<YourDocument>('YourModel', YourSchema); // 请求参数类型 interface UpdateRequest { docId: string; clientLastUpdatedTimestamp: number; } async function updateDocumentIfTimestampMatches(req: UpdateRequest) { await mongoose.connect('mongodb://localhost:27017/your-database-name'); try { // 执行原子更新操作 const updateResult = await YourModel.findOneAndUpdate( { _id: req.docId, lastUpdatedTimestamp: req.clientLastUpdatedTimestamp }, { $set: { lastUpdatedTimestamp: Date.now() } }, { rawResult: true } // 获取原生更新结果用于判断 ); if (updateResult?.rawResult?.nModified === 1) { return { success: true }; } else { return { success: false, message: '时间戳不匹配或文档不存在,更新失败' }; } } finally { await mongoose.disconnect(); } }
关键说明
- 原子性保证:
updateOne或findOneAndUpdate都是MongoDB的原子操作,服务器会原子性完成「条件匹配」与「字段更新」,不会出现中间状态。 - 结果判断:通过
modifiedCount(原生驱动)或nModified(Mongoose原生结果)判断是否有文档被更新。若值为0,说明请求的时间戳与数据库中当前值不匹配,大概率是文档已被其他请求修改。 - 时间戳格式兼容:如果你的文档使用
ISODate类型存储时间戳,只需将条件和更新值替换为new Date(req.clientLastUpdatedTimestamp)和new Date()即可。
内容的提问来源于stack exchange,提问作者HelmBurger
相关产品推荐
相关产品推荐

