Как проверить транзакции MySQL?
У меня вопрос о тестировании запросов в транзакции. Я уже давно использую транзакции MySQL, и каждый раз, когда я это делаю, я использую что-то вроде:
$doCommit = true;
$error = "";
mysql_query("BEGIN");
/* repeat this part with the different queries in the transaction
this often involves updating of and inserting in multiple tables */
$query = "SELECT, UPDATE, INSERT, etc";
$result = mysql_query($query);
if(!$result){
$error .= mysql_error() . " in " . $query . "<BR>";
$doCommit = false;
}
/* end of repeating part */
if($doCommit){
mysql_query("COMMIT");
} else {
echo $error;
mysql_query("ROLLBACK");
}
Теперь часто бывает, что я хочу проверить свою транзакцию, поэтому я меняю mysql_query("COMMIT");
на mysql_query("ROLLBACK");
, но могу себе представить, что это не очень хороший способ проверить этот материал. Обычно не представляется возможным копировать каждую таблицу в temp_table и обновлять ее и вставлять в эти таблицы и удалять их впоследствии (например, потому что таблицы могут быть очень большими). Разумеется, когда код переходит в производство, исправляется обработка ошибок (вместо того, чтобы просто печатать ошибку).
Какой лучший способ сделать такие вещи?
Ответы
Ответ 1
Прежде всего, в вашей реализации есть ошибка. В случае ошибки запроса текущая транзакция автоматически откатывается и затем закрывается. Так как вы продолжаете выполнять запросы, они не будут находиться в транзакции (они будут переданы в БД). Затем, когда вы выполняете Rollback
, он будет терпеть неудачу. Из Документы MySQL:
Rolling back can be a slow operation that may occur implicitly without the user
having explicitly asked for it (for example, when an error occurs).
Явная команда Rollback
должна использоваться только в том случае, если вы определили в приложении, которое вам нужно откат (по причинам, отличным от ошибки запроса). Например, если вы вычитаете средства из учетной записи, вы явно откажетесь, если узнаете, что у пользователя недостаточно средств для завершения обмена...
Что касается проверки транзакций, я копирую базу данных. Я создаю новую базу данных и устанавливаю набор "фиктивных данных". Затем я запускаю все тесты с помощью автоматизированного инструмента. Инструмент будет фактически совершать транзакции и отменять откаты и проверять, что ожидаемое состояние базы данных поддерживается на протяжении всех тестов. Поскольку сложнее запрограммировать информацию о состоянии конца транзакции, если у вас есть неизвестный ввод транзакции, тестирование живых (или даже скопированных с живого) данных будет непростым. Вы можете сделать это (и должны), но не зависеть от этих результатов для определения того, работает ли ваша система. Используйте эти результаты для создания новых тестовых примеров для автоматизированного тестера...
Ответ 2
Может быть, вы могли бы реорганизовать свой первый пример и использовать некоторый класс оболочки доступа к БД?
В этом классе оболочки вы можете иметь переменную $normalCommit = true;
и метод SetCommitMode(), который устанавливает переменную $normalCommit.
И у вас есть метод Commit(), который фиксирует if ($ normalCommit == true)
Или даже иметь переменную $failTransaction, которая вызывает mysql_query ( "ROLLBACK" ); если вы хотите (чтобы вы могли пройти или не пройти много последовательных тестов).
Затем, когда вы запускаете тест, вы можете установить где-нибудь в файле тестового кода:
$ MyDBClass- > SetCommitMode (ложь);
или
$ MyDBClass- > RollBackNextOperation (истина);
перед операцией, которую вы хотите провалить, и она просто потерпит неудачу. Таким образом, код, который вы тестируете, не будет содержать эти проверки fail/commit, только класс DB будет содержать их.
И обычно ONLLY тестовый код (особенно если вы выполняете модульное тестирование) должен вызывать эти методы SetCommitMode и RollBackNextOperation, поэтому вы случайно не оставляете эти вызовы в производственном коде.
Или вы можете передать некоторые сумасшедшие данные вашему методу (если вы тестируете метод), например, отрицательные переменные для сохранения в полях UNSIGNED, а затем ваша транзакция должна потерпеть неудачу на 100%, если ваш код не делает фиксацию после такого SQL ошибка (но это не должно).
Ответ 3
Обычно я использую что-то вроде (я использую pdo для моего примера):
$db->beginTransaction();
try {
$db->exec('INSERT/DELETE/UPDATE');
$db->commit();
}
catch (PDOException $e) {
$db->rollBack();
// rethrow the error or
}
Или, если у вас есть собственный обработчик исключений, используйте специальное предложение для своих PDOExceptions, где откат выполнения. Пример:
function my_exception_handler($exception) {
if($exception instanceof PDOException) {
// assuming you have a registry class
Registry::get('database')->rollBack();
}
}