iry*_*iry 7 concurrency locking mongodb
如果两个线程都读写同一个文档:
try (ClientSession clientSession = client.startSession()) {
clientSession.startTransaction();
result = collection.find(clientSession, keyOfDoc);
if (result blah blah blah) {
// Change the doc
collection.insertOne(clientSession, doc);
}
clientSession.commitTransaction();
}
Run Code Online (Sandbox Code Playgroud)
从事务的目的来看,一个线程应该得到另一个线程的编辑版本。
但是,当两个线程开始事务时,它们都获得了读锁,然后读取了文档。这两个线程都获得了旧版本的文档。并且当他们需要写文档时,他们会尝试获取写锁,这将使事务不是原子的。
另一种情况是写-写冲突。
try (ClientSession clientSession = client.startSession()) {
clientSession.startTransaction();
collection.insertOne(clientSession, docDifferent);
collection.insertOne(clientSession, docSame);
clientSession.commitTransaction();
}
Run Code Online (Sandbox Code Playgroud)
两个线程先获取不同文档的写锁,然后获取同一个文档的写锁,又是一个事务冲突。
MongoDB 使用什么级别的锁?我知道他们在 2.2 版之前使用实例级别,而自 4.0 版起支持事务。如果 MongoDB 不使用数据库级锁,那么 MongoDB 如何处理事务冲突?或者如果它使用数据库级锁,它如何处理读写冲突?
我在 MongoDB 手册中找到了一些参考资料,它们解决了我自己的问题。
MongoDB 使用什么类型的锁定?
MongoDB 使用多粒度锁定1允许操作锁定在全局、数据库或集合级别,并允许单个存储引擎在集合级别以下(例如,在 WiredTiger 中的文档级别)实现自己的并发控制。
MongoDB 使用从集合、数据库到全局的多级锁定。但是,尽管它支持多级锁定,但您可以访问的唯一级别是集合级别,这意味着您不能在事务中创建或删除数据库或集合。这也意味着在一个集合中获取一个要锁定的文档将导致整个集合被锁定。
受限操作
多单据交易中不允许进行以下操作:
影响数据库目录的操作,例如创建或删除集合或索引。例如,多文档事务不能包含会导致创建新集合的插入操作。
listCollections 和 listIndexes 命令及其辅助方法也被排除在外。
非 CRUD 和非信息性操作,例如 createUser、getParameter、count 等及其助手。
为了解决冲突,MongoDB会在发生冲突时向访问者发送无法获取锁的错误消息。
重试事务
无论 retryWrites 是否设置为 ,事务内的单个写操作都不可重试
true。如果操作遇到错误,返回的错误可能有一个 errorLabels 数组字段。如果错误是暂时性错误,则 errorLabels 数组字段包含“TransientTransactionError”作为元素,可以重试整个事务。
这意味着当访问者收到MongoException异常时.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL),访问者应该关闭会话,并重做事务。访问者应该重做并重新提交,直到提交成功。
您可以简单地使用此方法(从手动示例修改):
public static <T> T transactWithRetry(Callable<T> transactional) throws Exception {
while (true) {
try {
return transactional.call();
} catch (MongoException ex) {
if (!ex.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL)) throw ex;
}
}
}
Run Code Online (Sandbox Code Playgroud)
请参阅手册中更多语言的版本;)!
参考