Рассмотрим следующий сценарий:
- Я открываю новое соединение с MySQL, используя расширение mysqli
- Я начинаю сделку
- Я делаю несколько запросов к базе данных MySQL и к некоторым вычислениям php
- Либо MySQL, либо php могут вызывать некоторые исключения
- Я совершаю транзакцию
(псевдо) код:
$sql = new mysqli();
...
{ //some scope
$sql->query("START TRANSACTION");
...
if (something)
throw Exception("Something went wrong");
...
$sql->query("COMMIT"); // or $sql->commit()
} //leave scope either normally or through exception
...
$sql->query( ... ); // other stuff not in transaction
Если все вышеперечисленное находится в каком-то большом блоке try catch
(или, возможно, даже исключение не перехвачено), транзакция останется незавершенной. Если я затем попытаюсь еще поработать с базой данных, эти действия будут принадлежать транзакции.
Мой вопрос: существует ли какой-то возможный механизм, который позволил бы мне автоматически отправлять $sql->rollback()
, когда я выхожу из области создания транзакции ненормальным образом?
Некоторые примечания:
mysqli::autocommit
- это не то же самое, что транзакция. Это просто настройка для включения или выключения автоматической фиксации. Эквивалентно MySQLSET autocommit
.mysqli::begin_transaction
не задокументирован, и я не знаю, что его точное поведение. Будет ли он вызыватьrollback
, например, когда объектmysqli
умирает?
4 ответа
Вы не можете автоматически выполнять откат при исключении, но с небольшим кодом вы можете делать то, что хотите. Даже если вы уже находитесь в блоке try-catch
, вы можете вложить их в свой раздел транзакций, например:
try {
$sql->query("START TRANSACTION");
...
if (something)
throw new PossiblyCustomException("Something went wrong");
...
$sql->query("COMMIT");
} catch (Exception $e) { //catch all exceptions
$sql->query("ROLLBACK");
throw $e; //rethrow the very same exception object
}
Даже если вы используете самый общий catch Exception
, фактический тип исключения известен. Когда вы бросаете повторно, PossiblyCustomException
все еще может поймать его позже. Таким образом, этот новый блок try-catch
не повлияет на всю вашу уже имеющуюся обработку.
Таким образом, вы хотите либо зафиксировать транзакцию, либо откатить транзакцию, если возникла проблема, и есть много способов, по которым проблема могла возникнуть - либо через ошибку mysql, либо через php, генерирующий исключение. Так почему бы не установить флаг в начале транзакции, установить этот флаг, когда транзакция готова к фиксации, и не проверить состояние флага, когда процесс завершен, чтобы увидеть, нужен ли откат?
Если вы боитесь глобальных переменных, вы можете использовать для этого слишком сложный класс Singleton или какую-то статическую переменную, но вот основная идея:
function main() {
global $commit_flag = false;
start_transaction();
doEverything();
if ($commit_flag) {
commit_transaction();
} else {
rollback_transaction();
}
}
function doEverything() {
global $commit_flag;
try {
/* do some stuff that may cause errors */
$commit_flag = true;
} catch { ... }
}
Вы можете обрабатывать исключения mysql, используя синтаксис DECLARE ... HANDLER, но я не считаю правильным пытаться обрабатывать их через PHP!
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
START TRANSACTION;
INSERT INTO Users ( user_id ) VALUES('1');
INSERT INTO Users ( user_id ) VALUES('2');
COMMIT;
END;
Более подробную информацию можно найти здесь : http://dev.mysql.com/doc/refman/5.5 /en/declare-handler.html
Команда mysqli::begin_transaction
такая же (объектно-ориентированная), что и ваш $sql->query("START TRANSACTION");
;
Нет возможности автоматического отката при исключении.
Вы можете выполнить только комит, если все будет успешно. Тогда это будет "авто откат", если нет. Но в этом случае очень скоро у вас возникнут проблемы, если у вас будет более одной фиксации.
Итак, ваш текущий код уже очень хорош. Я бы сделал это полностью ООП:
$sql->begin_transaction();
try {
$sql->query('DO SOMETHING');
if(!true) {
throw new \Exception("Something went wrong");
}
$sql->commit();
}
catch (\Exception exception) {
$sql->rollback();
}
Вы также можете написать собственное исключение:
class SqlAutoRollbackException extends \Exception {
function __construct($msg, Sql $sql) {
$sql->rollback();
parent::__construct($msg);
}
}
Но поймать его все равно нужно.
Похожие вопросы
Новые вопросы
php
PHP — это широко используемый язык сценариев общего назначения с открытым исходным кодом, мультипарадигмальный, динамически типизированный и интерпретируемый, изначально разработанный для веб-разработки на стороне сервера. Используйте этот тег для вопросов о программировании на языке PHP.