架空地图模拟器免安装绿色中文版
653M · 2025-11-11
作为 NoSQL 数据库的代表,MongoDB 的事务能力一直被部分开发者“低估”——不少人还抱着“老版本有坑”“NoSQL 事务不靠谱”的固有印象。但实际上,自 4.0 版本支持多文档事务后,MongoDB 已实现 ACID 兼容,其基于快照隔离(Snapshot Isolation)的设计,不仅能媲美传统关系型数据库的一致性,还通过独特的冲突处理机制兼顾了性能。本文结合完整实测代码和案例,带大家彻底搞懂 MongoDB 的事务隔离级别,理清那些流传已久的误解。
MongoDB 的多文档事务采用快照隔离(Snapshot Isolation) 机制,依托多版本并发控制(MVCC)实现强一致性,完全满足 ACID 特性。
很多开发者会下意识将其与 SQL 标准的隔离级别对标,但这种做法并不恰当——SQL 标准的隔离级别定义并未考虑 MVCC,而 MongoDB、PostgreSQL 等主流数据库均依赖 MVCC 实现并发控制。简单来说:
readConcern(读关注级别)控制,不同级别对应不同的一致性保障和使用场景。MongoDB 的事务隔离核心由readConcern参数控制,不同级别对应不同的一致性表现,且无法直接等同于 SQL 标准的隔离级别,具体特点如下:
为了验证 MongoDB 事务的隔离能力,测试者遵循 Martin Kleppmann 的测试框架(原本用于 PostgreSQL),针对多文档事务场景进行了一系列实测。所有测试均采用readConcern: majority和writeConcern: majority配置(单节点环境),部分跨分片场景需使用snapshot级别,以下是带完整代码的关键测试结果:
所有测试均初始化基础数据:
// 初始化数据
use test_db;
db.test.drop();
db.test.insertMany([
{ _id: 1, value: 10 },
{ _id: 2, value: 20 }
]);
事务均通过startSession()开启,明确指定读/写关注级别。
// 初始化数据(同上,略)
// 事务T1
const session1 = db.getMongo().startSession();
const T1 = session1.getDatabase("test_db");
session1.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// 事务T2
const session2 = db.getMongo().startSession();
const T2 = session2.getDatabase("test_db");
session2.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// 两个事务同时更新同一文档
T1.test.updateOne({ _id: 1 }, { $set: { value: 11 } });
T2.test.updateOne({ _id: 1 }, { $set: { value: 12 } });
// 执行结果:抛出写冲突错误
// MongoServerError[WriteConflict]: Caused by :: Write conflict during plan execution and yielding is disabled. :: Please retry your operation or multi-document transaction.
const db = db.getMongo().getDB("test_db");
print(`Elapsed time: ${
((startTime = new Date())
&& db.test.updateOne({ _id: 1 }, { $set: { value: 12 } }))
&& (new Date() - startTime)
} ms`);
// 执行结果:Elapsed time: 72548 ms(等待约60秒超时后成功)
// 此时T1事务因超时被中止,提交时会报错:
// session1.commitTransaction();
// MongoServerError[NoSuchTransaction]: Transaction with { txnNumber: 2 } has been aborted.
// 初始化数据(同上,略)
// 事务T1
const session1 = db.getMongo().startSession();
const T1 = session1.getDatabase("test_db");
session1.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// 事务T2
const session2 = db.getMongo().startSession();
const T2 = session2.getDatabase("test_db");
session2.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// T1更新文档但未提交
T1.test.updateOne({ _id: 1 }, { $set: { value: 101 } });
// T2读取文档,仅能看到已提交数据
T2.test.find();
// 执行结果:[ { _id: 1, value: 10 }, { _id: 2, value: 20 } ]
// T1回滚事务
session1.abortTransaction();
// T2再次读取,结果不变
T2.test.find();
// 执行结果:[ { _id: 1, value: 10 }, { _id: 2, value: 20 } ]
session2.commitTransaction();
majority或snapshot级别均能杜绝“读取未提交数据”的异常。// 初始化数据(同上,略)
// 事务T1
const session1 = db.getMongo().startSession();
const T1 = session1.getDatabase("test_db");
session1.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// 事务T2
const session2 = db.getMongo().startSession();
const T2 = session2.getDatabase("test_db");
session2.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// T1更新文档(未提交)
T1.test.updateOne({ _id: 1 }, { $set: { value: 101 } });
// T2读取,看不到未提交变更
T2.test.find();
// 执行结果:[ { _id: 1, value: 10 }, { _id: 2, value: 20 } ]
// T1修改并提交
T1.test.updateOne({ _id: 1 }, { $set: { value: 11 } });
session1.commitTransaction();
// T2再次读取,仍看不到T1提交的新值
T2.test.find();
// 执行结果:[ { _id: 1, value: 10 }, { _id: 2, value: 20 } ]
session2.commitTransaction();
// 初始化数据(同上,略)
// 事务T1
const session1 = db.getMongo().startSession();
const T1 = session1.getDatabase("test_db");
session1.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// 事务T2
const session2 = db.getMongo().startSession();
const T2 = session2.getDatabase("test_db");
session2.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// T1更新文档1,T2更新文档2
T1.test.updateOne({ _id: 1 }, { $set: { value: 11 } });
T2.test.updateOne({ _id: 2 }, { $set: { value: 22 } });
// T1读取文档2,看不到T2的未提交更新
T1.test.find({ _id: 2 });
// 执行结果:[ { _id: 2, value: 20 } ]
// T2读取文档1,看不到T1的未提交更新
T2.test.find({ _id: 1 });
// 执行结果:[ { _id: 1, value: 10 } ]
// 提交两个事务
session1.commitTransaction();
session2.commitTransaction();
// 初始化数据(同上,略)
// 事务T1
const session1 = db.getMongo().startSession();
const T1 = session1.getDatabase("test_db");
session1.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// 事务T2
const session2 = db.getMongo().startSession();
const T2 = session2.getDatabase("test_db");
session2.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// 事务T3
const session3 = db.getMongo().startSession();
const T3 = session3.getDatabase("test_db");
session3.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// T1更新两个文档
T1.test.updateOne({ _id: 1 }, { $set: { value: 11 } });
T1.test.updateOne({ _id: 2 }, { $set: { value: 19 } });
// T2更新文档1,触发冲突
T2.test.updateOne({ _id: 1 }, { $set: { value: 12 } });
// 执行结果:抛出写冲突错误
// MongoServerError[WriteConflict]: Caused by :: Write conflict during plan execution and yielding is disabled. :: Please retry your operation or multi-document transaction.
// 初始化数据(同上,略)
// 事务T1
const session1 = db.getMongo().startSession();
const T1 = session1.getDatabase("test_db");
session1.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// 事务T2
const session2 = db.getMongo().startSession();
const T2 = session2.getDatabase("test_db");
session2.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// T1查询value=30的文档,无结果
T1.test.find({ value: 30 }).toArray();
// 执行结果:[]
// T2插入匹配条件的文档并提交
T2.test.insertOne({ _id: 3, value: 30 });
session2.commitTransaction();
// T1再次查询,仍无结果
T1.test.find({ value: { $mod: [3, 0] } }).toArray();
// 执行结果:[]
session1.commitTransaction();
// 初始化数据(同上,略)
// 事务T1
const session1 = db.getMongo().startSession();
const T1 = session1.getDatabase("test_db");
session1.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// 事务T2
const session2 = db.getMongo().startSession();
const T2 = session2.getDatabase("test_db");
session2.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// T1批量更新所有文档
T1.test.updateMany({}, { $inc: { value: 10 } });
// T2删除value=20的文档,触发冲突
T2.test.deleteMany({ value: 20 });
// 执行结果:抛出写冲突错误
// MongoServerError[WriteConflict]: Caused by :: Write conflict during plan execution and yielding is disabled. :: Please retry your operation or multi-document transaction.
// 初始化数据(同上,略)
// 事务T1
const session1 = db.getMongo().startSession();
const T1 = session1.getDatabase("test_db");
session1.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// 事务T2
const session2 = db.getMongo().startSession();
const T2 = session2.getDatabase("test_db");
session2.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// 两个事务均读取文档1的旧值
T1.test.find({ _id: 1 });
// 执行结果:[ { _id: 1, value: 10 } ]
T2.test.find({ _id: 1 });
// 执行结果:[ { _id: 1, value: 10 } ]
// 两个事务基于旧值执行更新,后执行的触发冲突
T1.test.updateOne({ _id: 1 }, { $set: { value: 11 } });
T2.test.updateOne({ _id: 1 }, { $set: { value: 11 } });
// 执行结果:抛出写冲突错误
// MongoServerError[WriteConflict]: Caused by :: Write conflict during plan execution and yielding is disabled. :: Please retry your operation or multi-document transaction.
// 初始化数据(同上,略)
// 事务T1
const session1 = db.getMongo().startSession();
const T1 = session1.getDatabase("test_db");
session1.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// 事务T2
const session2 = db.getMongo().startSession();
const T2 = session2.getDatabase("test_db");
session2.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// T1读取文档1和2
T1.test.find({ _id: 1 });
// 执行结果:[ { _id: 1, value: 10 } ]
T2.test.find({ _id: 2 });
// 执行结果:[ { _id: 2, value: 20 } ]
// T2更新两个文档并提交
T2.test.updateOne({ _id: 1 }, { $set: { value: 12 } });
T2.test.updateOne({ _id: 2 }, { $set: { value: 18 } });
session2.commitTransaction();
// T1再次读取文档2,结果不变
T1.test.find({ _id: 2 });
// 执行结果:[ { _id: 2, value: 20 } ]
session1.commitTransaction();
// 初始化数据(同上,略)
// 事务T1
const session1 = db.getMongo().startSession();
const T1 = session1.getDatabase("test_db");
session1.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// 事务T2
const session2 = db.getMongo().startSession();
const T2 = session2.getDatabase("test_db");
session2.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// T1查询value能被5整除的文档
T1.test.findOne({ value: { $mod: [5, 0] } });
// 执行结果:{ _id: 1, value: 10 }
// T2更新文档1使其能被3整除并提交
T2.test.updateOne({ value: 10 }, { $set: { value: 12 } });
session2.commitTransaction();
// T1查询value能被3整除的文档,无结果
T1.test.find({ value: { $mod: [3, 0] } }).toArray();
// 执行结果:[]
session1.commitTransaction();
// 初始化数据(同上,略)
// 事务T1
const session1 = db.getMongo().startSession();
const T1 = session1.getDatabase("test_db");
session1.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// 事务T2
const session2 = db.getMongo().startSession();
const T2 = session2.getDatabase("test_db");
session2.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// T1读取文档1
T1.test.find({ _id: 1 });
// 执行结果:[ { _id: 1, value: 10 } ]
// T2更新两个文档并提交
T2.test.updateOne({ _id: 1 }, { $set: { value: 12 } });
T2.test.updateOne({ _id: 2 }, { $set: { value: 18 } });
session2.commitTransaction();
// T1删除value=20的文档,触发冲突
T1.test.deleteMany({ value: 20 });
// 执行结果:抛出写冲突错误
// MongoServerError[WriteConflict]: Caused by :: Write conflict during plan execution and yielding is disabled. :: Please retry your operation or multi-document transaction.
MongoDB 的快照隔离并非万能,以下两种异常无法通过数据库层面自动防止,需要应用层介入处理:
// 初始化数据(同上,略)
// 事务T1
const session1 = db.getMongo().startSession();
const T1 = session1.getDatabase("test_db");
session1.startTransaction({
readConcern: { level: "majority" },
writeConcern: { w: "majority" }
});
// 事务T2
const session2 = db.getMongo().startSession();
const T2 = session2.getDatabase("test_db");
session2.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
});
// 两个事务均读取文档1和2
T1.test.find({ _id: { $in: [1, 2] } });
// 执行结果:[ { _id: 1, value: 10 }, { _id: 2, value: 20 } ]
T2.test.find({ _id: { $in: [1, 2] } });
// 执行结果:[ { _id: 1, value: 10 }, { _id: 2, value: 20 } ]
// 两个事务分别更新文档
T2.test.updateOne({ _id: 1 }, { $set: { value: 11 } });
T2.test.updateOne({ _id: 2 }, { $set: { value: 21 } });
// 两个事务均提交成功,未触发冲突
session1.commitTransaction();
session2.commitTransaction();
// 初始化数据(同上,略)
// 事务T1
const session1 = db.getMongo().startSession();
const T1 = session1.getDatabase("test_db");
session1.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
});
// 事务T2
const session2 = db.getMongo().startSession();
const T2 = session2.getDatabase("test_db");
session2.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
});
// 两个事务均查询value能被3整除的文档,无结果
T1.test.find({ value: { $mod: [3, 0] } }).toArray();
// 执行结果:[]
T2.test.find({ value: { $mod: [3, 0] } }).toArray();
// 执行结果:[]
// T1插入两个能被3整除的文档并提交
T1.test.insertOne({ _id: 3, value: 30 });
T1.test.insertOne({ _id: 4, value: 42 });
session1.commitTransaction();
// T2提交(无更新操作)
session2.commitTransaction();
// 最终查询,能看到T1插入的文档
T1.test.find({ value: { $mod: [3, 0] } }).toArray();
// 执行结果:[ { _id: 3, value: 30 }, { _id: 4, value: 42 } ]
readConcern: majority即可满足大部分需求,多分片场景需用snapshot保证跨分片一致性;如果你的业务需要使用 MongoDB 的多文档事务,不妨直接复制本文的测试代码进行验证,结合自身业务特点选择合适的读关注级别,同时在应用层处理好写倾斜和反依赖循环问题,即可充分发挥其高并发+强一致性的优势~
653M · 2025-11-11
3.75G · 2025-11-11
1.1G · 2025-11-11