Транзакции в MongoDB

May 24, 2020

Особенности работы на примере PHP

В MongoDB еще в версии 4.2 добавили честные ACID транзакции. MongoDB - это уже не просто key-value хранилище наподобие Redis (как некоторые используют), а все же более серьезная база данных.

Многие разработчики до сих пор скептически относятся к NoSQL базам в качестве основной базы. Если рассматривать монгу, то да, сехмы все еще приходится контролировать на стороне клиента и описывать в приложении через разные  ORM, Data Mappers и Active Records библиотеки.

У нас на проекте MongoDB используется как основная база данных и пока с ней не было проблем. Я уже привык к агрегатам вместо сложных SQL запросов, а так же привык описывать схемы в коде и контролировать целостность базы через приложение.

Одно из интересных преимуществ монги для стартапа - не нужно заморачиваться с миграциями. Только миграции на инициализацию индексов. Вы выкатываете код и новый код уже работает с новой схемой. Правда в коде нужно дополнительно создавать проверки на наличие того или иного поля. Но может это и не так уж и плохо.

Возвращаясь к транзакциям...

В PHP на данный момент офицальный драйвер - это MongoDB Driver. Не всегда очевидно как с ним работать, поэтому для своих проектов написал обертку для более удобного манипулирования.

Когда вы почитаете документацию и начнете работать с транзакциями, вдруг выяснится что они не работают, но при этом никаких ошибок не выдается... Первая ошибка, которая встречается - это то, что транзакционную сессию нужно передавать во все запросы, которые должны попасть в транзакцию:

<?php declare(strict_types=1);

// …

try { $session = $client->startSession(); $session->startTransaction();

$db-&gt;vacancies-&gt;delete(
	['_creator' =&gt; $user_id,
	['session' =&gt; $session]
);
$db-&gt;users-&gt;deleteOne(
	['_id' =&gt; $user_id,
	['session' =&gt; $session]
);

$session-&gt;commitTransaction();

} catch(Exception $e) { $session->abortTransaction(); }

// …

Но когда вы напишите правильно запросы, может оказаться что ваша база данных не поддерживает транзакции, если база запущена в single mode, по сути по дефолту, не в режиме replica set и будет выдавать что-то типа:

transactions are available for replica sets only

Если вы используете Docker с официального репозитория, то инициализировать MongoDB в докер контейнере через docker-compose можно следующим образом:

version : ‘3’
services:

MongoDB

mongo:
    image: mongo:4.2.3-bionic
    hostname: mongo
    container_name: mongo
    command: --auth --replSet rs0
    restart: always
    environment:
        TZ: "Europe/Moscow"
        MONGO_INITDB_ROOT_USERNAME: "${MONGO_USER}"
        MONGO_INITDB_ROOT_PASSWORD: "${MONGO_PASS}"
    ports:
        - 127.0.0.1:27017:27017

#EOF#

Собственно через команду

command: —replSet rs0

по сути мы устанавливаем имя нашего сервера rs0.

После этого запускаем контейнер:

docker-compose up -d

Проверить настройки сервера можно командой:

// in mongo shell:

db.runCommand({getCmdLineOpts:1});

Затем нужно всего один раз зайти в базу (можно через консоль, можно через GUI - как вы привыкли) и выполнить команду:

// in mongo shell

rs.initiate()

Вам должна показаться статистика, где будет показана конфигурация. В итоге вы получили кластер из одного сервера MongoDB.

{
“info2” : “no configuration specified. Using a default configuration for the set”,
“me” : “mongo:27017”,
“ok” : 1
}

Теперь можно проверить, что всё работает прямо в базе через запросы или в нашем PHP коде.

По идее после этого все должно заработать и у вас появится возможность использовать транзакции. Я так же рекомендую заглянуть в документацию и прочитать об ограничениях и возможностях транзакций.

Cannot create namespace in multi-document transaction

MongoDB славен тем, как я уже упоминал, что вы можете не создавать заранее коллекцию (таблицу в терминах реляционных СУБД), и она автоматически создастся при первой вставке.

НО! Если вы используете транзакции с несуществующими коллекциями, то вы получите сообщение об ошибке. В документации есть про это следующее:

Operations that affect the database catalog, such as creating or dropping a collection or an index, are not allowed in multi-document transactions. For example, a multi-document transaction cannot include an insert operation that would result in the creation of a new collection. See Restricted Operations.

Вам нужно заранее создать коллекцию.

db.createCollection(‘collection_name’);



Profile picture

Written by Alexander Mayorov
Full Stack CTO