Есть ли транзакции в MongoDB
Что проверяют
Знаете ли вы, что ответ «в MongoDB транзакций нет» устарел, и понимаете ли, когда они действительно нужны. Следом обычно просят показать код на C# драйвере и объяснить, что делать при временной ошибке.
Как отвечать
- атомарность одного документа: любая запись в один документ, включая вложенные массивы и поддокументы, атомарна всегда. Поэтому при схеме с вложением данных многодокументные транзакции нужны редко;
- многодокументные транзакции: с версии 4.0 на replica set, с 4.2 на шардированных кластерах. На одиночном сервере (standalone) они не работают, для локальной разработки поднимают replica set из одного узла;
- свойства: ACID с изоляцией на уровне снимка. Для надёжности берут read concern
snapshotи write concernmajority. По умолчанию транзакция живёт не дольше 60 секунд, это параметрtransactionLifetimeLimitSeconds; - API в C#:
IClientSessionHandleизclient.StartSessionAsync(), затемStartTransaction(), операции иCommitTransactionAsync()илиAbortTransactionAsync(). Удобнееsession.WithTransactionAsync(...): метод сам повторяет транзакцию при ошибке с меткойTransientTransactionErrorи коммит приUnknownTransactionCommitResult.
Каждая операция внутри транзакции получает сессию первым аргументом:
await accounts.UpdateOneAsync(session, a => a.Id == from, debit);
await accounts.UpdateOneAsync(session, a => a.Id == to, credit);Скажите и о цене: долгие транзакции нагружают кэш WiredTiger и конфликтуют с параллельными записями. Если связанные данные можно хранить в одном документе, одна атомарная запись дешевле транзакции.
Где спотыкаются
Забывают передать session в одну из операций. Она выполнится вне транзакции и не откатится вместе с остальными, причём никакой ошибки не будет.
Не обрабатывают повторы. Конфликт записи внутри транзакции это штатная ситуация, и без WithTransactionAsync или своего цикла повтора запрос просто падает у пользователя.
Тестируют на локальном standalone-сервере и получают ошибку о том, что номера транзакций разрешены только на участнике replica set или mongos. Решение: запустить mongod с --replSet и выполнить rs.initiate().