Рассмотрим следующий сценарий:
(псевдо) код:
$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
- это не то же самое, что транзакция. Это просто настройка автоматического включения или выключения. Эквивалент MySQL SET autocommit
.mysqli::begin_transaction
не документирован, и я не знаю, каково его точное поведение. Будет ли он ссылаться на rollback
когда объект mysqli
умирает, например? Вы не можете автоматически откатываться от исключения, но с небольшим кодом вы можете делать то, что хотите. Даже если вы уже находитесь в блоке 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
.
Команда 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);
}
}
Но вам все равно нужно поймать его.
Таким образом, вы хотите либо совершить транзакцию, либо отменить транзакцию, если возникла проблема, и есть много способов, которые могут возникнуть проблемы - либо с помощью ошибки 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
$sql
которые могут быть вызваны после обработки исключения. На данный момент я не знаю, нахожусь ли я в режиме транзакции или нет.