MongoDB Transactions у Laravel

Перекладено ШІ 0 Laravel News 26 червня, 2026

Laravel та MongoDB дозволяють будувати надійні архітектури завдяки підтримці multi-document ACID transactions. Розповідаємо, як забезпечити цілісність даних у вашому проєкті, зберігаючи гнучкість schema-less сховища.

Laravel — один із найпопулярніших PHP-фреймворків. Розробники цінують його за елегантний синтаксис, виразну ORM та багатий набір інструментів «з коробки». MongoDB, своєю чергою, стала пріоритетним вибором для гнучкого зберігання даних без чіткої схеми (schema-less), що легко масштабується. Разом вони створюють потужний стек, який поєднує продуктивність Laravel із гнучкістю MongoDB у роботі з даними сучасних застосунків.

Під час розробки проєктів рівня production критично важливою стає цілісність даних (data integrity). Незалежно від того, чи керуєте ви фінансовими транзакціями, залишками на складі або замовленнями, дані мають залишатися точними та узгодженими, навіть якщо кілька операцій відбуваються одночасно. Саме тут на допомогу приходять транзакції.

Раніше MongoDB вважалася нетранзакційною базою даних. Вона забезпечувала швидкість і гнучкість, але їй бракувало гарантій атомарності для кількох документів, до яких розробники звикли у SQL-системах. Усе змінилося з виходом MongoDB 4.0, де з'явилися багатодокументні ACID-транзакції. Тепер розробники можуть користуватися перевагами гнучкої схеми та бути впевненими у безпеці даних, коли операції потребують узгодженості між декількома документами чи колекціями.

У цій статті ми розберемося, як працюють транзакції в MongoDB та як їх використовувати у Laravel-застосунках. Ми почнемо з основ, розглянемо реалізацію ACID-властивостей у MongoDB, а потім перейдемо до конкретних прикладів у Laravel. Ви побачите, як транзакції інтегруються у типові сценарії, як-от керування замовленнями чи обробка платежів. Також ми обговоримо найкращі практики, поширені помилки та випадки, коли доречніше покластися на модель документів MongoDB, аніж загортати все у транзакцію.

Наприкінці ви чітко розумітимете, як впроваджувати та оптимізувати транзакції MongoDB у Laravel для створення швидких, гнучких та надійних застосунків.

# Розуміння транзакцій у базах даних

Транзакція в базах даних — це одиниця роботи, сукупність операцій, які мають бути виконані або всі разом, або жодна з них. Транзакції створюють захисний бар’єр, що запобігає потраплянню в базу даних неповних або суперечливих змін.

Щоб формалізувати таку поведінку, бази даних спираються на ACID-властивості:

  • Atomicity (Атомарність): усі операції в транзакції сприймаються як єдине ціле. Якщо одна операція не вдається, скасовується вся транзакція (rollback). Наприклад, при переказі коштів, якщо гроші списано з одного рахунку, але не зараховано на інший, атомарність запобігає збереженню часткових змін.

  • Consistency (Узгодженість): гарантує, що база даних переходить з одного валідного стану в інший. Після завершення транзакції всі обмеження, індекси та правила мають бути дотримані. Наприклад, якщо правило забороняє від’ємний баланс, узгодженість гарантує це після кожної операції.

  • Isolation (Ізоляція): запобігає впливу паралельних транзакцій одна на одну. Уявіть, що два клієнти одночасно купують останній товар на складі. Ізоляція гарантує, що кінцева кількість товару відобразить обидві операції коректно.

  • Durability (Стійкість): гарантує, що після підтвердження транзакції (commit) її результати збережуться навіть у разі збою системи чи перезавантаження.

У реляційних базах даних, як-от MySQL або PostgreSQL, ACID-транзакції є стандартом. MongoDB спочатку гарантувала атомарність лише на рівні окремого документа. Кожне оновлення одного документа було безпечним, але узгодженість між документами мав контролювати сам застосунок. Зі зростанням складності завдань MongoDB еволюціонувала, додавши багатодокументні транзакції, зберігши при цьому свою гнучку модель документів.

Транзакції критично важливі для бізнес-логіки.

У банківських системах переказ коштів вимагає списання з одного рахунку та зарахування на інший. Без транзакцій одна частина операції може завершитися успішно, а інша — ні.

В e-commerce підтвердження замовлення передбачає оновлення залишків, створення запису про замовлення та списання коштів. Транзакції гарантують успіх усіх цих кроків одночасно.

Коротко кажучи, транзакції — це фундамент довіри до застосунків, де точність і надійність є пріоритетом. Далі ми розглянемо, як MongoDB реалізує ці гарантії і як їх використовувати у Laravel.

# Модель транзакцій у MongoDB

На початку свого шляху MongoDB забезпечувала атомарність лише на рівні документа. Це означало, що будь-яка зміна одного документа була гарантовано цілісною. Такий підхід добре працював для багатьох сценаріїв, де вкладання пов'язаних даних в один документ позбавляло потреби в транзакціях між кількома записами.

Однак із поширенням MongoDB в ентерпрайз-середовищі розробникам знадобилися сильніші гарантії узгодженості між різними колекціями. Це призвело до появи багатодокументних ACID-транзакцій у версії MongoDB 4.0.

# Еволюція транзакцій у MongoDB

  • До версії 4.0: атомарними були лише операції з одним документом. Розробникам доводилося ретельно проєктувати схеми, часто вкладаючи пов'язані дані в один документ, щоб уникнути неузгодженості.
  • MongoDB 4.0: додано багатодокументні транзакції для replica sets.
  • MongoDB 4.2 і пізніше: впроваджено підтримку для sharded clusters, що зробило транзакції доступними навіть у горизонтально масштабованих середовищах.

Ці зміни наблизили MongoDB до транзакційних гарантій реляційних баз даних, зберігши при цьому гнучкість документо-орієнтованої моделі.

# Як працюють транзакції

У MongoDB транзакції використовують об'єкт сесії (session object) для групування операцій. Ви відкриваєте сесію, виконуєте операції у кількох колекціях, а потім або підтверджуєте (commit), або скасовуєте (abort) їх. MongoDB координує ці зміни, щоб забезпечити виконання ACID-властивостей.

Ось як виглядає процес транзакції у Laravel (PHP) з використанням сесій MongoDB:

use Illuminate\Support\Facades\DB;
 
DB::connection('mongodb')->transaction(function ($session) {
    // 1) Створення замовлення
    DB::connection('mongodb')->collection('orders')->insert([
        'item' => 'Laptop',
        'qty'  => 1,
        'created_at' => now(),
    ], ['session' => $session]);
 
    // 2) Зменшення залишків на складі
    DB::connection('mongodb')->collection('inventory')
        ->where('sku', 'LAP123')
        ->decrement('stock', 1, [], ['session' => $session]);
});

Об'єкт $session відіграє ключову роль. Він слугує спільним контекстом для всіх операцій у транзакції, дозволяючи MongoDB розпізнавати їх як єдину атомарну послідовність.

Явно передаючи $session у кожен запит, ви гарантуєте, що MongoDB сприйматиме ці операції як частину однієї транзакції. Без цього база даних виконувала б кожен запит окремо, втрачаючи гарантію атомарності.

Метод DB::connection('mongodb')->transaction() автоматично відкриває сесію та забезпечує атомарність. Якщо виникає помилка, транзакція скасовується. Якщо все гаразд — підтверджується.

# Обмеження та застереження

Попри свою потужність, транзакції мають певні нюанси:

Вплив на продуктивність: транзакції створюють додаткове навантаження, оскільки базі даних потрібно керувати блокуваннями, синхронізувати записи та відстежувати стан сесій. Тривалі або складні транзакції можуть сповільнювати систему.

Таймаути: за замовчуванням транзакції мають ліміт часу життя у 60 секунд. Якщо операції занадто повільні через великі обсяги даних або мережеві затримки, транзакція може бути автоматично скасована.

Дозволені операції: деякі дії не можна виконувати всередині транзакції. Наприклад, створення або видалення індексів, виконання DDL-команд або операції, що потребують глобальних блокувань. Це потрібно робити поза транзакційним контекстом.

Використання ресурсів: кожна транзакція споживає додаткову пам'ять для зберігання блокувань, записів у oplog та знімків даних (snapshots). Чим більше операцій у транзакції, тим вище навантаження на ресурси системи.

# Транзакції vs. вкладені документи

У MongoDB при моделюванні даних зазвичай є два шляхи: вкладати (embedding) пов'язані документи у батьківський або зберігати їх у різних колекціях, використовуючи посилання (referencing).

Вкладення часто взагалі знімає потребу в транзакціях, оскільки оновлення одного документа завжди атомарне. Наприклад, ви можете зберігати замовлення та всі його позиції в одному JSON-документі. Оновлення замовлення, додавання нового товару або зміна статусу оплати відбудуться атомарно без жодних багатодокументних транзакцій.

Натомість, якщо дані логічно належать до різних колекцій, як-от orders, payments та inventory, транзакції необхідні для підтримання узгодженості між ними. Вибір залежить від структури даних та способів доступу до них. Вкладення краще для локальності та швидкості, а посилання та транзакції — для розподілу обов'язків та цілісності між колекціями.

Гнучка схема MongoDB дозволяє уникати транзакцій у багатьох випадках завдяки вкладенню даних. Транзакції варто залишати для сценаріїв, де зв'язки охоплюють кілька колекцій і вимагають суворої узгодженості.

Тепер, розуміючи модель транзакцій MongoDB, ми готові налаштувати Laravel для роботи з ними.

# Налаштування Laravel та MongoDB для роботи з транзакціями

Транзакції не працюватимуть без коректної конфігурації середовища. Перш ніж переходити до реальних прикладів, закладемо фундамент.

Багатодокументні транзакції в MongoDB потребують replica set (або sharded cluster). Якщо ви використовуєте MongoDB Atlas, це вже налаштовано. Для локальної розробки потрібно запустити MongoDB як replica set (наприклад, через Docker з параметром --replSet або відповідною конфігурацією docker-compose).

# Крок 1: Встановлення пакету

Встановіть офіційний пакет Laravel MongoDB через Composer. Рекомендована версія — ^5.5, що сумісна з Laravel 11 та PHP 8.3.

composer require mongodb/laravel-mongodb:^5.5

# Крок 2: Налаштування змінних оточення

Відкрийте файл .env та вкажіть дані для підключення. Якщо ви користуєтеся MongoDB Atlas, скопіюйте рядок підключення з вашої панелі керування:

DB_CONNECTION=mongodb
MONGODB_URI="mongodb+srv://<username>:<password>@cluster0.mongodb.net"
MONGODB_DATABASE=laravel_mongo_tx_demo

Примітка: Формат +srv використовується для MongoDB Atlas. Для локального запуску URI може виглядати так: mongodb://127.0.0.1:27017/?replicaSet=rs0.

# Крок 3: Оновлення config/database.php

Додайте налаштування підключення у файл конфігурації баз даних Laravel:

// config/database.php
return [
    'connections' => [
        'mongodb' => [
            'driver'   => 'mongodb',
            'dsn'      => env('MONGODB_URI'),
            'database' => env('MONGODB_DATABASE'),
        ],
    ],
];

# Крок 4: Створення моделі MongoDB

Моделі працюють схоже на Eloquent-моделі SQL, але успадковують інший базовий клас:

use MongoDB\Laravel\Eloquent\Model;
 
class HealthCheck extends Model
{
    protected $connection = 'mongodb';
    protected $collection = 'health_checks';
    protected $fillable = ['ok'];
}

# Крок 5: Перевірка підключення

Перевірте роботу підключення через Laravel Tinker або простий роут.

Варіант А: Laravel Tinker

php artisan tinker
HealthCheck::create(['ok' => true]);
HealthCheck::all();

Варіант Б: Роут у routes/web.php

Route::get('/test-mongo', function () {
    HealthCheck::create(['ok' => true]);
    return HealthCheck::all();
});

Якщо ви бачите створений документ у відповіді або через MongoDB Compass, налаштування завершено.

# Виконання транзакцій у Laravel

MongoDB забезпечує транзакції через групування операцій у client session. Пакет Laravel MongoDB надає доступ до цього механізму, дозволяючи явно керувати сесіями та логікою транзакцій.

# Логіка роботи

Основний алгоритм транзакції в MongoDB через Laravel виглядає так:

  • Запуск клієнтської сесії.
  • Виконання операцій читання/запису всередині колбеку withTransaction.
  • Автоматичний commit, якщо колбек завершився успішно, або abort, якщо виникла помилка.
  • Передача об'єкта сесії у кожну операцію для участі в транзакції.

# Приклад: Грошовий переказ

Розглянемо переказ між гаманцями двох користувачів. Обидві операції (списання та зарахування) мають бути успішними одночасно.

use Illuminate\Support\Facades\DB;
use MongoDB\BSON\ObjectId;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
 
function transfer(string $from, string $to, int $amount): void
{
    $conn   = DB::connection('mongodb');
    $client = $conn->getMongoClient();
    $db     = $conn->getMongoDB();
 
    $session = $client->startSession([
        'defaultTransactionOptions' => [
            'readConcern'     => new ReadConcern(ReadConcern::LOCAL),
            'writeConcern'    => new WriteConcern(WriteConcern::MAJORITY),
            'readPreference'  => new ReadPreference(ReadPreference::PRIMARY),
        ],
    ]);
 
    $session->withTransaction(function () use ($db, $session, $from, $to, $amount) {
        $users = $db->selectCollection('users');
 
        // Перевірка балансу відправника
        $fromDoc = $users->findOne([
            '_id' => new ObjectId($from),
            'balance' => ['$gte' => $amount],
        ], ['session' => $session]);
 
        if (!$fromDoc) {
            throw new RuntimeException('Недостатньо коштів.');
        }
 
        // Списання та зарахування
        $users->updateOne(
            ['_id' => new ObjectId($from)],
            ['$inc' => ['balance' => -$amount]],
            ['session' => $session]
        );
 
        $users->updateOne(
            ['_id' => new ObjectId($to)],
            ['$inc' => ['balance' => $amount]],
            ['session' => $session]
        );
 
        // Логування операції
        $db->selectCollection('transfers')->insertOne([
            'from'    => new ObjectId($from),
            'to'      => new ObjectId($to),
            'amount'  => $amount,
            'created_at' => now(),
        ], ['session' => $session]);
    });
}

Ключовий момент — передача ['session' => $session] у кожну операцію.

# Поєднання Eloquent з транзакціями

Ви можете використовувати Eloquent-моделі всередині транзакції:

use MongoDB\Laravel\Eloquent\Model;
 
class User extends Model {
    protected $connection = 'mongodb';
    protected $collection = 'users';
    protected $fillable   = ['name','balance'];
}
 
function addStoreCredit(string $userId, int $amount): void
{
    $conn   = DB::connection('mongodb');
    $client = $conn->getMongoClient();
    $db     = $conn->getMongoDB();
 
    $session = $client->startSession();
 
    $session->withTransaction(function () use ($session, $db, $userId, $amount) {
        $db->selectCollection('users')->updateOne(
            ['_id' => new ObjectId($userId)],
            ['$inc' => ['balance' => $amount]],
            ['session' => $session]
        );
 
        $fresh = User::where('_id', new ObjectId($userId))->first();
        // Подальша логіка з $fresh
    });
}

# Обробка помилок та повтори

  • Виняток (exception) всередині колбеку автоматично скасовує транзакцію.
  • У розподілених системах можуть виникати тимчасові помилки (наприклад, переобрання primary вузла), що позначаються як TransientTransactionError. У таких випадках варто реалізувати механізм повторних спроб (retry).

# Перевірка продуктивності

Транзакції споживають ресурси, тому вони мають бути короткими та ефективними. Уникайте тривалого очікування користувача всередині транзакції та мінімізуйте кількість операцій. Для складних процесів краще використовувати вкладені документи, де це можливо.

# Просунуті сценарії та реальні приклади

Транзакції корисні всюди, де кілька колекцій залежать одна від одної. Розглянемо практичні кейси.

# Робочий процес e-commerce замовлення

При створенні замовлення кілька операцій мають відбутися синхронно:

  • Списання товару зі складу.
  • Створення документа замовлення.
  • Оновлення балансу або бонусів користувача.
DB::connection('mongodb')->transaction(function ($session) use ($userId, $productId, $quantity) {
    $db       = DB::connection('mongodb')->getMongoDB();
    $products = $db->selectCollection('products');
    $orders   = $db->selectCollection('orders');
    $users    = $db->selectCollection('users');
 
    $product = $products->findOne(
        ['_id' => new ObjectId($productId)],
        ['projection' => ['price' => 1, 'stock' => 1], 'session' => $session]
    );
 
    if (! $product || ($product['stock'] ?? 0) < $quantity) {
        throw new RuntimeException('Недостатньо товару на складі.');
    }
 
    $total = ($product['price'] ?? 0) * $quantity;
 
    $products->updateOne(
        ['_id' => new ObjectId($productId)],
        ['$inc' => ['stock' => -$quantity]],
        ['session' => $session]
    );
 
    $orders->insertOne([
        'user_id'    => new ObjectId($userId),
        'product_id' => new ObjectId($productId),
        'quantity'   => $quantity,
        'total'      => $total,
        'status'     => 'confirmed',
        'created_at' => now(),
    ], ['session' => $session]);
 
    $users->updateOne(
        ['_id' => new ObjectId($userId)],
        ['$inc' => ['balance' => -$total]],
        ['session' => $session]
    );
});

Цей приклад гарантує, що якщо будь-який крок не вдасться (наприклад, не вистачить коштів), списання товару зі складу буде автоматично скасовано.

# Транзакції проти вкладених документів: що обрати?

У MongoDB є два основні способи структурування даних: вкладення (embedding) або посилання (referencing). Вибір між транзакціями та вкладенням — ключова навичка при проєктуванні застосунків на MongoDB.

# Вкладені документи: природний підхід

Замість розділення даних на багато таблиць, ви зберігаєте їх в одному документі. Наприклад, замовлення та список товарів у ньому:

{
  "_id": ObjectId("..."),
  "customer_id": ObjectId("..."),
  "items": [
    { "product": "Laptop", "quantity": 1 },
    { "product": "Mouse", "quantity": 2 }
  ],
  "status": "processing"
}

Оскільки MongoDB гарантує атомарність на рівні одного документа, оновлення статусу замовлення та зміна кількості товарів у масиві відбуваються атомарно без потреби у транзакціях.

# Коли транзакції необхідні

Транзакції потрібні тоді, коли дані мають різні життєві цикли та зберігаються у різних колекціях (наприклад, замовлення в одній, а платіжні транзакції — в іншій). Якщо платіж не пройшов, статус замовлення не має змінитися.

# Таблиця вибору стратегії

Сценарій Рекомендований підхід
Замовлення з позиціями Вкладення (Embedding). Усі деталі замовлення в одному документі для швидкого доступу та атомарності.
Профіль та налаштування користувача Вкладення. Налаштування та преференції в документі користувача для спрощення оновлень.
Замовлення та оплата Транзакції. Якщо оплата та замовлення — це різні колекції, транзакція гарантує їх синхронність.
Фінансові перекази Транзакції. Необхідні для одночасного списання з одного рахунку та зарахування на інший.
Логи та аудит Окремі колекції. Транзакції потрібні лише якщо логи мають бути суворо синхронізовані зі станом сутностей.

# Найкращі практики та помилки

  1. Тримайте транзакції короткими: вони блокують документи. Чим довше триває транзакція, тим вища ймовірність конфліктів.
  2. Використовуйте індекси: запити всередині транзакцій мають бути швидкими. Індексуйте поля, за якими робите пошук чи оновлення.
  3. Не забувайте передавати $session: найпоширеніша помилка — забути передати об'єкт сесії в одну з операцій. Це виведе операцію за межі транзакції.
  4. Обробляйте TransientTransactionError: у розподілених системах мережеві збої трапляються. Реалізуйте повторні спроби для критичних операцій.

# Висновок

Транзакції в MongoDB додають рівень узгодженості та надійності, який раніше був притаманний лише SQL-базам. Завдяки Laravel ви можете використовувати ці можливості за допомогою звичного та елегантного API.

Використовуйте транзакції там, де потрібна сувора цілісність даних у кількох колекціях, але не забувайте про силу вкладених документів MongoDB. Правильний баланс між цими підходами дозволить створювати надійні та швидкі застосунки, що легко масштабуються.

Популярні

Інше, що варто прочитати

11 Оновлено 26 червня, 2026

Генерація документації в Laravel за допомогою штучного інтелекту

Docudoodle — це потужний пакет для генерації документації в Laravel, який допомагає легко аналізувати вашу кодову базу та створювати документацію за допомогою обраного вами AI. Чи готові ви дізнатися, як цей інструмент може спростити вашу роботу з документуванням коду? Читайте далі!

26 Оновлено 26 червня, 2026

"SQLSTATE[HY000] [2002] Connection refused" у Laravel в GitHub Actions

Чи стикалися ви з помилкою «SQLSTATE[HY000] [2002] Connection refused» під час налаштування GitHub Actions для вашого додатку на Laravel? У нашій статті ми розглянемо три поширені причини цієї помилки та надамо рішення для їх усунення. Читайте далі, щоб дізнатися, як ваш CI/CD потік може працювати бездоганно!

50 Оновлено 26 червня, 2026

Nuxt 3 + Laravel Sanctum: Просте та надійне рішення для автентифікації вашого SPA та API

У сучасній веб-розробці аутентифікація є ключовою для захисту додатків і даних користувачів. Дізнайтеся, як модуль nuxt-sanctum-authentication спростить інтеграцію між Nuxt 3 та Laravel Sanctum, забезпечуючи надійний і зручний спосіб реалізації аутентифікації для вашого проєкту