Транзакции в goDB
Mysqli предоставляет следующие методы для реализации транзакций:
autocommit(),
commit() и
rollback().
Кроме этого, конечно, остаётся возможность реализовывать их непосредственно через SQL-запросы.
goDB дополняет эти возможности своими методами:
int transactionBegin([$saveAC = false]); // начало транзакции
bool transactionCommit(void); // сохранение транзакции
bool transactionRollback([bool $throw = false); // откат транзакции
mixed transactionRun(callback $callback [, $saveAC = false]); // выполнение функции в транзакции
bool transactionFailed(void); // не провалилась ли транзакция
int transactionLevel(void); // получить текущий уровень вложенности
bool inTransaction(void); // находимся ли мы внутри транзакции
bool transactionClose([bool $commit = false]); // закрыть транзакцию
Следует понимать, что goDB, как и mysqli не делает ничего невозможного — если движок таблицы не поддерживает транзакции, то их и не будет.
Простые транзакции
Набор методов обычен: BEGIN, COMMIT и ROLLBACK.
$db->transactionBegin();
// Внутри транзакции
$db->query($query1);
$db->query($query2);
$db->query($query3);
if ($condition) {
$db->transactionCommit(); // Сохранить транзакцию
} else {
$db->transactionRollback(); // Откатить транзакцию
}
// Уже вышли из транзакции
$db->query($query4);
Вложенные транзакции
/**
* Какая-то функция, выполняющая транзакцию
*/
function func1() {
$db->transactionBegin();
...
$db->transactionCommit();
}
/**
* Ещё одна функция, выполняющая транзакцию и вызывающая внутри неё, предыдущую
*/
function func2() {
$db->transactionBegin();
...
func1();
...
$db->transactionCommit();
}
Как видно, внутри одной транзакции могут быть вложенные транзакции. Причём вложенные могут не подозревать о том, что они вложенные.
goDB отслеживает уровень вложенности транзакций и вызывает непосредственное сохранение данных только при коммите на самом верхнем уровне.
Откат вложенных транзакций
Очевидно, что ошибка в любой части транзакции означает ошибку всей транзакции. Следовательно откат на любом уровне вложенности, должен вести к откату всей транзакии верхнего уровня. goDB предоставляет два способа для подобного отката.
Тихий откат
function func1() {
$db->transactionBegin();
...
if (!$condition) {
$this->transactionRollback();
return;
}
...
$db->transactionCommit();
}
$db->transactionBegin();
func1();
func2();
func3();
$db->transactionCommit();
Если где-то во вложенной транзакции вызывается transactionRollback(), код продолжает выполняться дальше,
однако все запросы (в том числе подготовленные и мультизапросы)
возвращают False.
Достижение же transactionCommit() верхнего уровня ведёт на самом деле к rollback'у и выходу из транзакции.
С помощью transactionFailed() внутри транзакции можно узнать не помечена ли она уже, как провалившаяся.
Откат с исключением
transactionRollback() с аргументом true, сбрасывает весь стек транзакций и генерирует исключение
goDBExceptionTransactionRollback.
function func1() {
$db->transactionBegin();
...
if (!$condition) {
$this->transactionRollback(true);
}
...
$db->transactionCommit();
}
try {
$db->transactionBegin();
func1();
func2();
func3();
$db->transactionCommit();
} catch (goDBExceptionTransactionRollback $e) {
}
Если нет уверенности, что мы находимся на верхнем уровне, лучше добавить проверку:
try {
$level = $db->transactionBegin(); // begin возвращает уровень вложенности
func1();
func2();
func3();
$db->transactionCommit();
} catch (goDBExceptionTransactionRollback $e) {
if ($level > 1) {
throw $e;
}
}
goDB-методы vs mysqli-методы
Вне транзакции организованной с помощью transactionBegin(), методы mysqli (autocommit() и др.)
ведут себя также, как и обычно. Внутри транзакции — несколько по другому.
Отличное поведение основывается на тех же предположениях про вложенные транзакции.
autocommit() и не имеют никакого эффекта.commit()
rollback() вызывает transactionRollback() без исключения.
Следует заметить, что goDB-транзакции исходят из предположения, что autocommit изначально True.
То есть по выходе из транзакций он будет установлен обратно в True.
Можно установить аргумент $saveAC в True, тогда значение автокоммита будет запоминаться.
Однако, это производится через лишний запрос к базе, так что решите нужно ли вам это.
int goDB::transactionBegin([bool $saveAc]);
bool goDB::transactionRun(callback $callback [, bool $saveAc]);
Кстати, введена дополнительная функция getAutocommit() возвращающая значение автокоммита.
transactionRun: выполнение функции в транзакции
$db->transactionRun(
function() use ($db) {
$db->query($query1);
$db->query($query2);
$db->query($query3);
return true;
}
);
Возвращение callback-функцией любого значения, приводящегося к True, расценивается, как корректное выполнение и
приводит к сохранению транзакции. Это же значение возвращается и из transactionRun().
Значения же, приводящиеся к False, приводят к откату транзакции.
В случае возникновения в callback-функции исключений, transactionRun ведёт себя следующим образом:
- В случае
goDBExceptionTransactionRollbackвследствии вызова внутри функцииtransactionRollback()воспринимается, как обычный откат. Исключение перехватывается и просто возвращаетсяFalse. - В случае любого другого godb-исключения (
goDBException), происходит откат транзакции, а исключение проваливается дальше. - Никакие другие исключения не перехватываются.
transactionClose: закрытие транзакции
transactionClose() вызывает выход из транзакции, на каком бы её уровне мы не находились.
По умолчанию приводит к откату всех уже сделанных изменений.
Указание же True в качестве единственного аргумента, приводит к их сохранению.
Следует использовать с крайней осторожностью. Например, так:
try {
func();
} catch (RuntimeException $e) {
$db->transactionClose();
}
Внутри вызова func() могут вызываться транзакции любого уровня вложенности.
В то же время там может быть выброшено какое-то исключение, которое, однако, не должно привести к завершению приложения.
На уровне, на котором оно будет перехвачено уже не должно быть никаких транзакций,
но счётчик вложенности останется на том уровне, когда произошло исключение.
Поэтому просто сбрасываем его: transactionClose().