База данных: Транзакции

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

Автоматические транзакции

Метод transaction — самый простой и безопасный способ. Он автоматически:

  1. Начинает транзакцию
  2. Выполняет ваш код
  3. Фиксирует изменения при успехе
  4. Откатывает при любой ошибке
use Imhotep\Facades\DB;

DB::transaction(function () {
    // Списание
    DB::update('UPDATE accounts SET balance = balance - ?', [100]);
    
    // Зачисление
    DB::update('UPDATE accounts SET balance = balance + ?', [100]);
    
    // Если здесь возникнет исключение — оба UPDATE откатятся
});
PHP

Повторные попытки при deadlock:

DB::transaction(function () {
    // Конфликтующие операции с другими транзакциями
    DB::update('UPDATE products SET stock = stock - ? WHERE id = ?', [1, 1]);
    DB::update('UPDATE orders SET status = ? WHERE id = ?', ['completed', 1]);
}, attempts: 5); // Повторить до 5 раз при взаимоблокировке
PHP

Важно: При deadlock внутри вложенной транзакции повторные попытки не выполняются — исключение выбрасывается сразу.

Ручное управление

Для полного контроля используйте методы beginTransaction, commit и rollBack:

use Imhotep\Facades\DB;

DB::beginTransaction();

try {
    DB::update('UPDATE accounts SET balance = balance - ?', [100]);
    DB::update('UPDATE accounts SET balance = balance + ?', [100]);
    
    // Проверка условий
    if ($someCondition) {
        throw new \Exception('Условие не выполнено');
    }
    
    DB::commit(); // Фиксируем изменения
    
} catch (\Exception $e) {
    DB::rollBack(); // Откатываем все изменения
    throw $e;
}
PHP

Вложенные транзакции

Imhotop поддерживает вложенные транзакции через механизм savepoints. Внутренние транзакции могут быть откатаны независимо от внешних.

use Imhotep\Facades\DB;

DB::transaction(function () {
    // Внешняя транзакция
    
    DB::update('UPDATE users SET status = ?', ['processing']);
    
    DB::transaction(function () {
        // Внутренняя транзакция (savepoint)
        // При ошибке откатывается только эта часть
        DB::update('UPDATE logs SET message = ?', ['started']);
        
        // Если здесь ошибка — откатится только savepoint, внешняя транзакция продолжит
    });
    
    DB::update('UPDATE users SET status = ?', ['completed']);
});
PHP

Уровень вложенности:

$level = DB::transactionLevel(); // Текущий уровень: 0, 1, 2, ...

DB::beginTransaction(); // level: 1
DB::beginTransaction(); // level: 2 (savepoint)

// ...

DB::rollBack(); // Откат до уровня 1
DB::commit();   // Фиксация внешней транзакции
PHP

Откат до определенного уровня

Метод rollBack принимает параметр $toLevel для отката до конкретного уровня:

DB::beginTransaction(); // level: 1
DB::beginTransaction(); // level: 2
DB::beginTransaction(); // level: 3

// Откат до уровня 1 (оставляет внешнюю транзакцию открытой)
DB::rollBack(1);

// Транзакция все еще активна
DB::commit(); // Фиксация
PHP

Обработка ошибок

Взаимоблокировки (Deadlock)

При возникновении deadlock Imhotep автоматически:

  • Откатывает транзакцию
  • Повторяет попытку (если указано в attempts)
  • После исчерпания попыток выбрасывает DeadlockException
use Imhotep\Contracts\Database\DeadlockException;

try {
    DB::transaction(function () {
        // Конфликтующие операции
    }, attempts: 3);
} catch (DeadlockException $e) {
    // Все попытки исчерпаны
    Log::error('Не удалось выполнить транзакцию из-за deadlock', [
        'message' => $e->getMessage()
    ]);
}
PHP

Потеря соединения

При потере соединения:

  1. Imhotep автоматически переподключается
  2. Восстанавливает транзакцию
  3. Продолжает выполнение

Если ошибка произошла при фиксации, уровень транзакции сбрасывается в 0.

Поддержка savepoints

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