Как правило, вы должны извлечь соответствующий сегмент кода в вопрос. Неразумно просить людей найти 5 соответствующих строк в вашей 76-строчной программе.
Ваш тест выполняет параллельные операции поиска и изменения. Предположим, что есть два параллельных процесса A и B, каждый из которых увеличивает баланс счета на 50. Начальный баланс равен 0. Порядок операций может быть следующим:
A: what is the current balance for account 1234?
B: what is the current balance for account 1234?
DB -> A: balance for account 1234 is 0
DB -> B: balance for account 1234 is 0
A: new balance is 0+50 = 50
A: set balance for account 1234 to 50
DB -> A: ok, new balance for account 1234 is 50
B: new balance is 0+50 = 50
B: set balance for account 1234 to 50
DB -> B: ok, new balance for account 1234 is 50
С точки зрения базы данных, нет «конфликтов записи» " Вот. Вы попросили установить баланс 50 для данной учетной записи дважды.
Существуют различные способы решения этой проблемы. Одним из них является использование условных обновлений таким образом, чтобы процесс выглядел следующим образом:
A: what is the current balance for account 1234?
B: what is the current balance for account 1234?
DB -> A: balance for account 1234 is 0
DB -> B: balance for account 1234 is 0
A: new balance is 0+50 = 50
A: if balance in account 1234 is 0, set balance to 50
DB -> A: ok, new balance for account 1234 is 50
B: new balance is 0+50 = 50
B: if balance in account 1234 is 0, set balance to 50
DB -> B: balance is not 0, no update was performed
B: err, let's start over
B: what is the current balance for account 1234?
DB -> B: balance for account 1234 is 50
B: new balance is 50+50 = 100
B: if balance in account 1234 is 50, set balance to 100
DB -> B: ok, new balance for account 1234 is 100
Как видите, база данных должна поддерживать условное обновление, а приложение должно обрабатывать возможность одновременных обновлений и повторять операцию.
Если баланс может go увеличиваться и уменьшаться, это практически не полезный способ написания дебетово-кредитной системы (но если баланс может только увеличиваться или уменьшаться, это на самом деле будет работать вполне нормально). В реальных системах вы должны использовать специальное поле, цель которого - указать конкретную c версию документа, существовавшего в тот момент, когда приложение получило некоторые данные; обновление обусловлено тем, что текущая версия документа остается неизменной, и каждое обновление увеличивает версию. Параллельные обновления будут тогда обнаружены, потому что номер версии является неправильным, а не полем контента.
Существуют способы вызвать «конфликт записи» на стороне базы данных, например, с помощью транзакций, поддерживаемых MongoDB 4.0+. , В принципе, это работает так же, но «версия» называется «идентификатором транзакции» и хранится в другом месте (не в строке в документе, с которым ведется работа). Но принцип тот же. В этом случае база данных сообщит вам, что произошел конфликт записи, вам все равно нужно будет повторно выполнить операции.
Обновление:
Я думаю, вам также необходимо различить guish между «оптимистическим c валютным контролем» как концепцией, ее реализацией и тем, к чему относится реализация. https://docs.mongodb.com/manual/faq/concurrency/#how -granular-are-locks-in-mongodb , например, говорит:
Для большинства операций чтения и записи WiredTiger использует оптимистичное c управление параллелизмом. WiredTiger использует только намеренные блокировки на глобальном уровне, уровне базы данных и уровне сбора. Когда механизм хранения обнаруживает конфликты между двумя операциями, возникает конфликт записи, в результате которого MongoDB прозрачно повторяет эту операцию.
Внимательно читая этот оператор, он применяется к операциям записи на уровне механизма хранения . Я представляю, когда MongoDB выполняет что-то вроде $set
или другие операции записи Atomi c, это применимо. Но это не относится к последовательностям операций уровня приложения, как вы указали в своем примере.
Если вы попробуете свой пример кода с вашей любимой реляционной СУБД, я думаю, вы обнаружите, что он производит примерно то же самое результат, который вы видели с MongoDB, , если вы выполняете транзакцию вокруг каждого отдельного чтения и записи (так что чтение и запись баланса находятся в разных транзакциях), по той же причине - СУБД блокируют данные (или используют такие методы, как MV CC) для срока действия транзакции, но не для разных транзакций.
Аналогично, если вы поместите и чтение, и запись баланса на одну и ту же учетную запись в транзакцию в MongoDB, вы можете обнаружить, что вы получают временные ошибки, когда другие транзакции одновременно изменяют учетную запись.
Наконец, API, который MongoDB реализует для транзакций (с повторными попытками), описан здесь . Если вы внимательно посмотрите на него, то обнаружите, что оно ожидает, что приложение повторно выдаст не только команду фиксации транзакции, но и всю операцию транзакции. Это связано с тем, что, как правило, при возникновении «конфликта записи» исходные данные изменились, и просто повторной попытки окончательной записи недостаточно - потенциально необходимо выполнить повторные вычисления в приложениях, возможно, даже побочные эффекты этого процесса изменятся как результат.