Знакомьтесь с новыми функциями MongoDB 4.0 — Транзакции

задняя часть база данных внешний интерфейс MongoDB
Знакомьтесь с новыми функциями MongoDB 4.0 — Транзакции

предисловие

Я считаю, что друзья, которые использовали основные реляционные базы данных, не слишком незнакомы с «транзакциями». Это позволяет нам интегрировать несколько операций базы данных с несколькими таблицами в одну атомарную операцию, что может быть гарантировано в сценариях с высоким параллелизмом. Несколько операций с данными не мешают друг с другом; и если в какой-либо части этих операций возникает ошибка, транзакция будет прервана, а данные будут откатываться, что обеспечивает согласованность данных при одновременном изменении данных в нескольких таблицах.

В прошлом MongoDB не поддерживала транзакции, поэтому, когда разработчикам нужно было использовать транзакции, им приходилось заимствовать другие инструменты, чтобы компенсировать недостатки базы данных на уровне бизнес-кода. С выпуском версии 4.0 MongoDB также предоставляет нам собственные операции транзакций. Давайте познакомимся с ними вместе и научимся использовать их на простых примерах.

вводить

Транзакции и наборы реплик

Набор реплик — это первичная и вторичная архитектура узла MongoDB, которая максимизирует доступность данных и позволяет избежать недоступности всей службы, вызванной единой точкой отказа. В настоящее время операции транзакций MongoDB с несколькими таблицами поддерживают работу только с наборами реплик.Если вы хотите установить и запустить наборы реплик в локальной среде, вы можете использовать набор инструментов —run-rs, следующие статьи содержат подробные инструкции по использованию:

она и компания barbarian.com/introduction…

Транзакции и сеансы

Транзакции связаны с сеансами (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. Добро пожаловать, чтобы оставить сообщение для обсуждения, мы ответим как можно скорее.

Спасибо за чтение.