【问题标题】:How to test MySQL transactions?如何测试 MySQL 事务?
【发布时间】:2010-08-10 11:01:49
【问题描述】:

我有一个关于在事务中测试查询的问题。我使用 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 并更新并插入这些表并在之后删除它们通常是不可行的(例如,因为表可能非常大)。当然,当代码投入生产时,相关的错误处理(而不仅仅是打印错误)就会到位。

做这样的事情最好的方法是什么?

【问题讨论】:

  • 天哪...刚发现 PHP 不支持 finallyensure 之类的东西。
  • 是的,我知道... PHP 很烂,我用它...
  • 现在支持finally,从 PHP 5.5 开始

标签: mysql debugging transactions


【解决方案1】:

首先,您的实现中有一个错误。如果查询出错,当前事务会自动回滚然后关闭。因此,当您继续执行查询时,它们将不在事务中(它们将被提交到数据库)。然后,当你执行Rollback 时,它会默默地失败。来自MySQL docs

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 仅应在您在应用程序中确定需要回滚时使用(出于查询错误以外的原因)。例如,如果您从账户中扣除资金,如果您发现用户没有足够的资金来完成兑换,您会明确回滚...

就交易测试而言,我确实复制了数据库。我创建了一个新数据库并安装了一组“虚拟数据”。然后我使用自动化工具运行所有测试。该工具将实际提交事务并强制回滚,并检查在整个测试过程中是否保持预期的数据库状态。由于如果您对交易有未知的输入,则很难以编程方式从交易中了解最终状态,因此测试实时(甚至是从实时复制的)数据并不容易。您可以(并且应该)这样做,但不要依赖这些结果来确定您的系统是否正常工作。使用这些结果为自动化测试人员构建新的测试用例...

【讨论】:

  • 哇,我还没有在这个上做正确的功课……我真的不知道发生错误时 MySQL 会回滚。感谢您的洞察力。也感谢其他人,这是一个非常有用的帖子。
  • 当然。我经常看到这个错误(因为如果你不期待它是违反直觉的)......当你通过查看执行日志试图找出为什么有几个查询时,这也是一个很难调试的问题即使执行了回滚命令,也提交了......这就是为什么我总是在查询错误时抛出异常(特别是考虑到生产代码不应该出错,除非真的出了问题(比如服务器消失了)。所以为什么还要继续尝试如果你不知道出了什么问题?)...这种方式也很难忽视...
  • 你是对的。在您的帖子中,您谈到了自动化测试仪,您是自己做的吗?我可以想象不同的项目需要不同的测试人员(甚至可能需要不同的测试用例)。
  • 我只是使用 PHPUnit。虽然它不是单元测试,但它仍然是一个很好的自动化测试框架。是的,每个项目都有自己的一组测试用例。实际上,我为每个模型使用了一组不同的测试用例......
  • 感谢您的回答,真的很有帮助。我要去看看 PHPUnit(我不得不承认我们在我工作的公司不使用单元测试。)这是一家小公司,所以我可能会尝试将它介绍给其他人。
【解决方案2】:

也许您可以重构您的第一个示例并使用一些 DB 访问包装类?

在那个包装类中你可以有一个变量 $normalCommit = true; 以及设置 $normalCommit 变量的方法 SetCommitMode()。 你有一个方法 Commit() 提交 if($normalCommit == true) 甚至有一个变量 $failTransaction 调用 mysql_query("ROLLBACK");如果你愿意(这样你就可以通过/失败许多顺序测试)。

然后当你运行测试时,你可以在测试代码文件的某处设置: $myDBClass->SetCommitMode(false); 或者 $myDBClass->RollBackNextOperation(true); 在您希望失败的操作之前,它只会失败。这样,您正在测试的代码将不包含那些失败/提交检查,只有 DB 类将包含它们。

通常只有测试代码(特别是如果您进行单元测试)应该调用这些 SetCommitMode 和 RollBackNextOperation 方法,因此您不会不小心将这些调用留在生产代码中。

或者你可以将一些疯狂的数据传递给你的方法(如果你正在测试一个方法),比如要保存在 UNSIGNED 字段中的负变量,然后如果你的代码在这样的 SQL 之后没有提交,那么你的事务应该 100% 失败错误(但不应该)。

【讨论】:

  • 感谢您的回复,这是一个明智的解决方案。如果我能平分赏金,你会得到 50%...
【解决方案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();
  }
}

【讨论】:

  • 是的,但是您如何测试您的交易?我的问题的重点是,我想知道如何在一个事务中测试所有查询。在您的情况下,我会将$db-&gt;commit(); 替换为$db-&gt;rollBack();,但这与我在原始问题中所做的相同。
猜你喜欢
  • 2019-04-22
  • 1970-01-01
  • 2016-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多