предисловие
Я считаю, что друзья, которые использовали основные реляционные базы данных, не слишком незнакомы с «транзакциями». Это позволяет нам интегрировать несколько операций базы данных с несколькими таблицами в одну атомарную операцию, что может быть гарантировано в сценариях с высоким параллелизмом. Несколько операций с данными не мешают друг с другом; и если в какой-либо части этих операций возникает ошибка, транзакция будет прервана, а данные будут откатываться, что обеспечивает согласованность данных при одновременном изменении данных в нескольких таблицах.
В прошлом MongoDB не поддерживала транзакции, поэтому, когда разработчикам нужно было использовать транзакции, им приходилось заимствовать другие инструменты, чтобы компенсировать недостатки базы данных на уровне бизнес-кода. С выпуском версии 4.0 MongoDB также предоставляет нам собственные операции транзакций. Давайте познакомимся с ними вместе и научимся использовать их на простых примерах.
вводить
Транзакции и наборы реплик
Набор реплик — это первичная и вторичная архитектура узла MongoDB, которая максимизирует доступность данных и позволяет избежать недоступности всей службы, вызванной единой точкой отказа. В настоящее время операции транзакций MongoDB с несколькими таблицами поддерживают работу только с наборами реплик.Если вы хотите установить и запустить наборы реплик в локальной среде, вы можете использовать набор инструментов —run-rs, следующие статьи содержат подробные инструкции по использованию:
Транзакции и сеансы
Транзакции связаны с сеансами (Sessions).Сеанс может открывать только одну транзакционную операцию за раз.При отключении сеанса транзакция в этом сеансе также завершается.
функции в транзакциях
-
Session.startTransaction()
Транзакция запускается в текущем сеансе, и операции с данными могут быть запущены после открытия транзакции. Операции с данными, выполняемые в транзакции, изолированы извне, то есть операции в транзакции являются атомарными.
-
Session.commitTransaction()
Зафиксируйте транзакцию, сохраните изменения данных в транзакции, а затем завершите текущую транзакцию.Операции с данными транзакции до фиксации невидимы для внешнего мира.
-
Session.abortTransaction()
Прервите текущую транзакцию и откатите изменения данных, выполненные в транзакции.
Повторить попытку
Когда во время операции транзакции сообщается об ошибке, объект ошибки, полученный функцией catch, будет содержать атрибут с именемerrorLabelsКогда массив содержит следующие 2 элемента, это означает, что мы можем повторно инициировать соответствующую операцию транзакции.
- TransientTransactionError: Происходит при открытии транзакции и последующих этапах обработки данных.
- UnknownTransactionCommitResult: Появляется на этапе фиксации транзакции.
Пример
После вышеупомянутого предзнаменования, не можете ли вы дождаться, чтобы узнать, как написать код для завершения полной операции транзакции? Ниже мы просто пишем пример:
Описание сценария:Предположим, что в торговой системе есть 2 таблицы: таблица товаров, в которой записывается такая информация, как название товара и количество на складе, и таблица заказов, в которой записывается заказ. Когда пользователь размещает заказ, он должен сначала найти соответствующий товар в таблице товаров, определить, соответствует ли количество запасов спросу в заказе, и если да, вычесть соответствующее значение, а затем вставить данные заказа в таблицу заказов. . В сценарии с высоким параллелизмом новый запрос на создание заказа может быть получен в процессе запроса количества запасов и сокращения запасов.В это время может возникнуть проблема, потому что, когда новый запрос запрашивает запасы, предыдущая операция все еще остается Операция сокращения запасов не завершена. В это время запрошенного количества запасов может быть достаточно, поэтому начинается последующая операция. Фактически, после того, как операция сокращения запасов была выполнена в последней операции, количество запасов уже недостаточно, поэтому размещается новый заказ. Запросы могут привести к тому, что фактическое количество созданного заказа превысит количество на складе.
Чтобы решить эту проблему в прошлом, мы можем использовать метод «блокировки» данных о продукте, например, на основе Redis.различные замки, только один заказ может работать с одними данными о продукте одновременно.Это решение может решить проблему.Недостаток заключается в том, что код более сложный, а производительность будет ниже. Если вы используете транзакции базы данных, это может быть намного проще:
Данные таблицы товаров (инвентарь — это инвентарь):
{ "_id" : ObjectId("5af0776263426f87dd69319a"), "name" : "灭霸原味手套", "stock" : 5 }
{ "_id" : ObjectId("5af0776263426f87dd693198"), "name" : "雷神专用铁锤", "stock" : 2 }
Данные таблицы заказов:
{ "_id" : ObjectId("5af07daa051d92f02462644c"), "commodity": ObjectId("5af0776263426f87dd69319a"), "amount": 2 }
{ "_id" : ObjectId("5af07daa051d92f02462644b"), "commodity": ObjectId("5af0776263426f87dd693198"), "amount": 3 }
Завершите операцию создания заказа (mongo Shell) одной транзакцией:
// 执行 txnFunc 并且在遇到 TransientTransactionError 的时候重试
function runTransactionWithRetry(txnFunc, session) {
while (true) {
try {
txnFunc(session); // 执行事务
break;
} catch (error) {
if (
error.hasOwnProperty('errorLabels') &&
error.errorLabels.includes('TransientTransactionError')
) {
print('TransientTransactionError, retrying transaction ...');
continue;
} else {
throw error;
}
}
}
}
// 提交事务并且在遇到 UnknownTransactionCommitResult 的时候重试
function commitWithRetry(session) {
while (true) {
try {
session.commitTransaction();
print('Transaction committed.');
break;
} catch (error) {
if (
error.hasOwnProperty('errorLabels') &&
error.errorLabels.includes('UnknownTransactionCommitResult')
) {
print('UnknownTransactionCommitResult, retrying commit operation ...');
continue;
} else {
print('Error during commit ...');
throw error;
}
}
}
}
// 在一次事务中完成创建订单操作
function createOrder(session) {
var commoditiesCollection = session.getDatabase('mall').commodities;
var ordersCollection = session.getDatabase('mall').orders;
// 假设该笔订单中商品的数量
var orderAmount = 3;
// 假设商品的ID
var commodityID = ObjectId('5af0776263426f87dd69319a');
session.startTransaction({
readConcern: { level: 'snapshot' },
writeConcern: { w: 'majority' },
});
try {
var { stock } = commoditiesCollection.findOne({ _id: commodityID });
if (stock < orderAmount) {
print('Stock is not enough');
session.abortTransaction();
throw new Error('Stock is not enough');
}
commoditiesCollection.updateOne(
{ _id: commodityID },
{ $inc: { stock: -orderAmount } }
);
ordersCollection.insertOne({
commodity: commodityID,
amount: orderAmount,
});
} catch (error) {
print('Caught exception during transaction, aborting.');
session.abortTransaction();
throw error;
}
commitWithRetry(session);
}
// 发起一次会话
var session = db.getMongo().startSession({ readPreference: { mode: 'primary' } });
try {
runTransactionWithRetry(createOrder, session);
} catch (error) {
// 错误处理
} finally {
session.endSession();
}
Вышеприведенный код выглядит большим, на самом деле две функции, runTransactionWithRetry и commitWithRetry, могут быть извлечены и стать публичными функциями, и нет необходимости повторять запись для каждой операции. После использования транзакции, поскольку операция с данными в транзакции является атомарной операцией, нам не нужно рассматривать проблему непротиворечивости данных, вызванную распределенным параллелизмом.Ощущается ли это намного проще?
Вы могли заметить, что код устанавливает два параметра при выполнении startTransaction — readConcern и writeConcern, которые являются уровнями подтверждения операций чтения и записи MongoDB и используются здесь для балансировки надежности и производительности операций чтения и записи данных в наборе реплик. , если здесь слишком много распространяться, поэтому заинтересованные друзья предлагают прочитать официальную документацию, чтобы узнать:
читатьКонцерн:
docs.MongoDB.com/master/ref E…
напишитеОбеспокоенность:
docs.MongoDB.com/master/ref E…
Текст / Тони абзац
Автор разрешил опубликовать эту статью, и авторские права принадлежат Chuangyu Frontend. Пожалуйста, укажите источник для перепечатки этой статьи. Ссылка на эту статью:известно Sec-Fed.com/2018-08-24-…
Если вы хотите подписаться на другие сообщения с передовой линии KnownsecFED, выполните поиск и подпишитесь на нашу общедоступную учетную запись WeChat: KnownsecFED. Добро пожаловать, чтобы оставить сообщение для обсуждения, мы ответим как можно скорее.
Спасибо за чтение.