База данных: Транзакции
Транзакции — это механизм, который гарантирует целостность данных при выполнении нескольких операций. Представьте, что вы переводите деньги со счета на счет: нужно списать сумму с одного счета и зачислить на другой. Если на втором шаге произойдет ошибка, первый шаг должен быть отменен. Транзакции решают эту задачу — либо все операции выполняются успешно, либо ни одна.
Автоматические транзакции
Метод transaction — самый простой и безопасный способ. Он автоматически:
- Начинает транзакцию
- Выполняет ваш код
- Фиксирует изменения при успехе
- Откатывает при любой ошибке
use Imhotep\Facades\DB;
DB::transaction(function () {
// Списание
DB::update('UPDATE accounts SET balance = balance - ?', [100]);
// Зачисление
DB::update('UPDATE accounts SET balance = balance + ?', [100]);
// Если здесь возникнет исключение — оба UPDATE откатятся
});
Повторные попытки при 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 раз при взаимоблокировке
Важно: При 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;
}
Вложенные транзакции
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']);
});
Уровень вложенности:
$level = DB::transactionLevel(); // Текущий уровень: 0, 1, 2, ...
DB::beginTransaction(); // level: 1
DB::beginTransaction(); // level: 2 (savepoint)
// ...
DB::rollBack(); // Откат до уровня 1
DB::commit(); // Фиксация внешней транзакции
Откат до определенного уровня
Метод rollBack принимает параметр $toLevel для отката до конкретного уровня:
DB::beginTransaction(); // level: 1
DB::beginTransaction(); // level: 2
DB::beginTransaction(); // level: 3
// Откат до уровня 1 (оставляет внешнюю транзакцию открытой)
DB::rollBack(1);
// Транзакция все еще активна
DB::commit(); // Фиксация
Обработка ошибок
Взаимоблокировки (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()
]);
}
Потеря соединения
При потере соединения:
- Imhotep автоматически переподключается
- Восстанавливает транзакцию
- Продолжает выполнение
Если ошибка произошла при фиксации, уровень транзакции сбрасывается в 0.
Поддержка savepoints
Если СУБД не поддерживает savepoints, вложенные транзакции будут игнорироваться (все операции выполняются в рамках одной транзакции).