MySQL / 15. Выполнение транзакций

Использование транзакций в программах на Perl

Задача
Вы хотите выполнить транзакцию в DBI-сценарии.

Решение
Используйте стандартный механизм поддержки транзакций DBI.

Обсуждение
Механизм реализации транзакции в DBI базируется на явном управлении режимом автофиксации. Процедура выглядит так:

1. Если это еще не сделано, включите атрибут RaiseError и выключите Print-Error. Вы хотите, чтобы ошибки порождали исключения и ничего не выводили; если же оставить PrintError включенным, в некоторых случаях это может помешать обнаружению ошибки.

2. Отключите атрибут AutoCommit, чтобы фиксация происходила только по вашему указанию.

3. Выполните предложения транзакции в блоке eval так, чтобы ошибки вызывали исключение и завершали блок. Последним в блоке должен быть вызов commit(), который фиксировал бы транзакцию в случае успешного выполнения всех ее предложений.

4. После выполнения eval проверьте переменную $@. Если она содержит пустую строку, транзакция выполнена успешно. В противном случае этоозначает, что произошла какая-то ошибка, и $@ будет содержать сообщение об ошибке. Вызовите rollback() для отмены транзакции. Если вы хотите отобразить для пользователя сообщение об ошибке, выведите $@ перед вызовом rollback().

Следующий код показывает, как реализовать процедуру выполнения нашей транзакции. Текущие значения атрибутов обработки ошибок и автофиксации сохраняются перед выполнением транзакции, а после ее выполнения возвращаются в исходное состояние. Для ваших приложений это может оказаться избыточным. Например, если вы знаете, что RaiseError и PrintError уже установлены так, как надо, нет необходимости в их сохранении и восстановлении.

# сохраняем атрибуты обработки ошибок и автофиксации,
# затем убеждаемся в том, что они установлены корректно.
$save_re = $dbh->{RaiseError};
$save_pe = $dbh->{PrintError};
$save_ac = $dbh->{AutoCommit};
$dbh->{RaiseError} = 1; # исключение в случае ошибки
$dbh->{PrintError} = 0; # не выводить сообщение об ошибке
$dbh->{AutoCommit} = 0; # отключить автофиксацию
eval
{
# передать немного денег от одного человека другому
$dbh->do ("UPDATE money SET amt = amt - 6 WHERE name = 'Eve'");
$dbh->do ("UPDATE money SET amt = amt + 6 WHERE name = 'Ida'");
# все предложения выполнены успешно, зафиксировать транзакцию
$dbh->commit ();
};
if ($@) # произошла ошибка
{
print "Transaction failed, rolling back. Error was:\n$@\n";
# откат внутри eval, чтобы ошибка отката не привела к завершению работы сценария
eval { $dbh->rollback (); };
}
# восстановить исходное состояние атрибутов
$dbh->{AutoCommit} = $save_ac;
$dbh->{PrintError} = $save_pe;
$dbh->{RaiseError} = $save_re;

Как видите, приходится проделать большую работу для того, чтобы выдать пару предложений. Чтобы упростить обработку транзакции, можно создать несколько функций, которые займутся обработкой до и после выполнения eval:

sub transact_init
{
my $dbh = shift;
my $attr_ref = {}; # создать хеш для хранения атрибутов$attr_ref->{RaiseError} = $dbh->{RaiseError};
$attr_ref->{PrintError} = $dbh->{PrintError};
$attr_ref->{AutoCommit} = $dbh->{AutoCommit};
$dbh->{RaiseError} = 1; # исключение в случае ошибки
$dbh->{PrintError} = 0; # не выводить сообщение об ошибке
$dbh->{AutoCommit} = 0; # отключить автофиксацию
return ($attr_ref); # вернуть атрибуты в вызывающую программу
}
sub transact_finish
{
my ($dbh, $attr_ref, $error) = @_;
if ($error) # произошла ошибка
{
print "Transaction failed, rolling back. Error was:\n$error\n";
# откат внутри eval, чтобы ошибка отката не привела
# к завершению работы сценария
eval { $dbh->rollback (); };
}
# восстановить исходное состояние атрибутов обработки ошибок и автофиксации
$dbh->{AutoCommit} = $attr_ref->{AutoCommit};
$dbh->{PrintError} = $attr_ref->{PrintError};
$dbh->{RaiseError} = $attr_ref->{RaiseError};
}
Если использовать две эти функции, наша транзакция значительно упрос-
тится:
$ref = transact_init ($dbh);
eval
{
# передать деньги от одного человека другому
$dbh->do ("UPDATE money SET amt = amt - 6 WHERE name = 'Eve'");
$dbh->do ("UPDATE money SET amt = amt + 6 WHERE name = 'Ida'");
# все предложения выполнены успешно, зафиксировать транзакцию
$dbh->commit ();
};
transact_finish ($dbh, $ref, $@);

Начиная с DBI 1.20 есть альтернатива ручному управлению атрибутом Auto-Commit – можно начинать транзакцию, вызывая begin_work(). Этот метод отключает AutoCommit и автоматически разрешает его при последующем вызове commit() или rollback().

Статьи по MySQL на эту тему:

Альтернативы транзакциям
Использование транзакций в программах на Java
Использование транзакций в программах на PHP
Использование транзакций в программах на Python

Вернуться в раздел: MySQL / 15. Выполнение транзакций